library(ggplot2)
Need help? Try Stackoverflow: https://stackoverflow.com/tags/ggplot2
Warning message:
In fun(libname, pkgname) :
  mzR has been built against a different Rcpp version (1.0.7)
than is installed on your system (1.0.8.3). This might lead to errors
when loading mzR. If you encounter such issues, please send a report,
including the output of sessionInfo() to the Bioc support forum at 
https://support.bioconductor.org/. For details see also
https://github.com/sneumann/mzR/wiki/mzR-Rcpp-compiler-linker-issue.
library(MSnbase)
Loading required package: BiocGenerics

Attaching package: ‘BiocGenerics’

The following objects are masked from ‘package:stats’:

    IQR, mad, sd, var, xtabs

The following objects are masked from ‘package:base’:

    anyDuplicated, append, as.data.frame, basename, cbind, colnames, dirname, do.call, duplicated, eval, evalq,
    Filter, Find, get, grep, grepl, intersect, is.unsorted, lapply, Map, mapply, match, mget, order, paste, pmax,
    pmax.int, pmin, pmin.int, Position, rank, rbind, Reduce, rownames, sapply, setdiff, sort, table, tapply, union,
    unique, unsplit, which.max, which.min

Loading required package: Biobase
Welcome to Bioconductor

    Vignettes contain introductory material; view with 'browseVignettes()'. To cite Bioconductor, see
    'citation("Biobase")', and for packages 'citation("pkgname")'.

Loading required package: mzR
Loading required package: Rcpp
Loading required package: S4Vectors
Loading required package: stats4

Attaching package: ‘S4Vectors’

The following objects are masked from ‘package:base’:

    expand.grid, I, unname

Loading required package: ProtGenerics

Attaching package: ‘ProtGenerics’

The following object is masked from ‘package:stats’:

    smooth


This is MSnbase version 2.20.4 
  Visit https://lgatto.github.io/MSnbase/ to get started.


Attaching package: ‘MSnbase’

The following object is masked from ‘package:base’:

    trimws
library(biobroom)
Loading required package: broom
library(camprotR)
library(Proteomics.analysis.data)
library(dplyr)

Attaching package: ‘dplyr’

The following object is masked from ‘package:MSnbase’:

    combine

The following objects are masked from ‘package:S4Vectors’:

    first, intersect, rename, setdiff, setequal, union

The following object is masked from ‘package:Biobase’:

    combine

The following objects are masked from ‘package:BiocGenerics’:

    combine, intersect, setdiff, union

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
library(tidyr)

Attaching package: ‘tidyr’

The following object is masked from ‘package:S4Vectors’:

    expand
library(here)
here() starts at /Users/tomsmith/git_repos/code_review/Biotin_pull_down_git
library(DEqMS)
Loading required package: matrixStats

Attaching package: ‘matrixStats’

The following object is masked from ‘package:dplyr’:

    count

The following objects are masked from ‘package:Biobase’:

    anyMissing, rowMedians

Loading required package: limma

Attaching package: ‘limma’

The following object is masked from ‘package:BiocGenerics’:

    plotMA
library(limma)
library(broom)

Input data.

We’ll start by reading in the data to a data.frame, which is a generic data.structure, suitable for any data table. This is the required input for CamprotR::parse_features and allows us to perform manual filtering with e.g dplyr easily

pep_data <- read.delim(here("2024_02_13_Peptide_data.txt"))

filter crap proteins

crap_fasta_inf <-here('2023_02_CCP_cRAP.fasta.gz')

# Load the cRAP FASTA used for the PD search
crap_fasta <- Biostrings::fasta.index(crap_fasta_inf, seqtype = "AA")

# Extract the UniProt accessions associated with each cRAP protein
crap_accessions <- crap_fasta %>%
  pull(desc) %>%
  stringr::str_extract_all(pattern="(?<=\\|).*?(?=\\|)") %>%
  unlist()
pep_data_flt <- camprotR::parse_features(
  pep_data,
  level = 'peptide',
  crap_proteins = crap_accessions,
  unique_master = FALSE
)
Parsing features...
5182 features found from 1191 master proteins => Input
250 cRAP proteins supplied
357 proteins identified as 'cRAP associated'
4869 features found from 1164 master proteins => cRAP features removed
4823 features found from 1136 master proteins => associated cRAP features removed
#level = 'peptide': This parameter specifies that you're interested in parsing peptide-level features from pep_data.
#crap_proteins = crap_accessions: This parameter seems to specify a vector (crap_accessions) containing protein accessions that are considered contaminants or irrelevant for your analysis. The function likely filters out peptides associated with these proteins.

Filter for unique master proteins

pep_data_flt <- pep_data_flt %>% filter(!grepl(';', Master.Protein.Accessions))
camprotR:::message_parse(pep_data_flt, 'Master.Protein.Accessions', "features with non-unique master proteins removed")
4581 features found from 1056 master proteins => features with non-unique master proteins removed
sample_data <-read.csv("Sample_data.csv")

# Displaying the table in a nicer format
knitr::kable(sample_data,
             align = "cccc",
             format = "html",
             table.attr = "style='width:30%;'")
File Sample Condition Replicate
1 NCL_VEH_1 NCL 1
2 NCL_LNA_1 NCL 1
3 NCL_ION_1 NCL 1
4 CL_VEH_1 CL 1
5 CL_LNA_1 CL 1
6 CL_ION_1 CL 1
7 NCL_VEH_2 NCL 2
8 NCL_LNA_2 NCL 2
9 NCL_ION_2 NCL 2
10 CL_VEH_2 CL 2
11 CL_LNA_2 CL 2
12 CL_ION_2 CL 2
13 NCL_VEH_3 NCL 3
14 NCL_LNA_3 NCL 3
15 NCL_ION_3 NCL 3
16 CL_VEH_3 CL 3
17 CL_LNA_3 CL 3
18 CL_ION_3 CL 3

Convert to an MsnSet

A MsnSet is a proteomics-specific data structure, with functions available for standard proteomics workflows. It’s since been deprecated and replaced by Qfeatures, which allows multiple levels of quantification to be stored in the same object, with details about how the features in each level are linked. Nonetheless, a MsnSet is still a sensible data structure to use for proteomics data. Many camprotR functions have been written explicitly to work with MsnSets, for example plot_quant in the code block after this.

=

Let’s QC the peptides

pep %>%
  log(base = 2) %>%
  camprotR::plot_quant(method = 'box')

let’s look at some cool peptide intensities

pep %>%
  log(base = 2) %>%
  camprotR::plot_quant(method = 'density') +
  scale_colour_manual(values=rep(get_cat_palette(6), each=3))

p <- MSnbase::plotNA(pep, pNA = 0) +
  camprotR::theme_camprot(border = FALSE, base_family = 'sans', base_size = 10) +
  labs(x = 'Peptide index')

naniar::gg_miss_upset is a generic function, not one specifically for proteomics data. As such, it was designed to take a data.frame() with just numeric columns. Hence we extract the exprs matrix from the MsnSet and convert to a data.frame

missing_data <- pep %>%
  exprs() %>%
  data.frame()

naniar::gg_miss_upset(missing_data,
                      sets = paste0(colnames(pep), '_NA'),
                      keep.order = TRUE,
                      nsets = 10)

I Don’t think we need to do any normalising to peptide intensities because we didn’t inject the same amount of peptide, it’s a pulldown.

Let’s do some summarising to protein-level abundances

Relaxing max NA to 2/3 and reducing min peptides per protein to 2 (from 3) doubles the retained peptides to ~2000.


pep_restricted <- pep %>%
  # Maximum 2/3 missing values
  MSnbase::filterNA(pNA = 2/3) %>% # With 18 sample, allowing missing in 12 seems reasonable to me.

  # At least two peptides per protein
  camprotR::restrict_features_per_protein(min_features = 2, plot = FALSE) %>%

  # Repeat the filtering since restrict_features_per_protein will replace some values with NA
  MSnbase::filterNA(pNA = 2/3) %>%

  camprotR::restrict_features_per_protein(min_features = 2, plot = FALSE)
p <- MSnbase::plotNA(pep_restricted, pNA = 0) +
  camprotR::theme_camprot(border = FALSE, base_family = 'sans', base_size = 15) +
  labs(x = 'Peptide index')

Warning message re missing values can be safely ignored here. MSnbase::combineFeatures doesn’t do any sanity checking that NA values are being appropriately handled and gives this warning so the onus is on the user to know what they are doing. method='robust' can handle NA values appropriately. Any proteins with NA value following this will be because the model could not estimate protein abundance from the peptide abundance. For example, in the following table, where X = peptide quantified, the protein cannot be quantified in sample A, since the peptides quantified in sample A are not quantified in any other sample. This is why we need to pre-filter the peptides to only retain the most informative ones

A B C D
pep1 X
pep2 X X X
pep3 X
pep4 X X X

prot_robust <- pep_restricted %>%
  log(base=2) %>%
  MSnbase::combineFeatures(
    # group the peptides by their master protein id
    groupBy = fData(pep_restricted)$Master.Protein.Accessions,
    method = 'robust',
    maxit = 1000  # Ensures convergence for MASS::rlm
  )
Your data contains missing values. Please read the relevant section in the combineFeatures manual page for details on
the effects of missing values on data aggregation.
p <- MSnbase::plotNA(prot_robust, pNA = 0) +
  camprotR::theme_camprot(border = FALSE, base_family = 'sans', base_size = 15)

naniar::gg_miss_upset(data.frame(exprs(prot_robust)),
                      sets = paste0(colnames(prot_robust), '_NA'),
                      keep.order = TRUE,
                      nsets = 10)

saveRDS(prot_robust, 'lfq_prot_robust.rds')

Shall we do some statistical testing?

Load in the QC’d LFQ data from the rds from before

lfq_protein <- readRDS('lfq_prot_robust.rds')

We need to convert the data structure from MsnSet to a data.frame to make it easy to filter to proteins with at least two replicate with quantification values for each set of samples. It would be possible to do this with the MsnSet, though I think it would prove pretty painful

Note, code below is an example for how to test CL vs NC for the 6 LNA samples. If you are performing many different comparisons, it may make sense to create a function(s) for some of this to avoid repeating the same code and having lots of similarly named objects around. I’ve indicated the most obvious parts where functions could be written. - - adding the counts to the limma object

———————Test LNA CL vs NCL data————————–


lfq_protein_tidy_lna <- lfq_protein %>%
  # make the MSnSet into 'tidy' format for further testing
  biobroom::tidy.MSnSet(addPheno=TRUE) %>% # addPheno=TRUE adds the phenotype columns
  subset(sample=="NCL_LNA_1"| sample=="NCL_LNA_2" | sample=="NCL_LNA_3" | sample=="CL_LNA_1"| sample=="CL_LNA_2" | sample=="CL_LNA_3") %>%
  filter(is.finite(value)) %>%
  group_by(protein, Condition) %>%
  filter(n()>=2) %>% 
  group_by(protein) %>%
  filter(length(unique(Condition))==2) %>%# n() is the length of the group
  ungroup()
Warning: `tbl_df()` was deprecated in dplyr 1.0.0.
Please use `tibble::as_tibble()` instead.
  
# This could be a function since it always be the same steps
# make_expr_wide <- function(tidy_expr) {}
filtered_exprs_lna <- lfq_protein_tidy_lna %>%
  pivot_wider(names_from=sample, values_from=value, id_cols=protein) %>%
  tibble::column_to_rownames('protein') %>%
  as.matrix()

# Since the column names for our filtered exprs matrix is in the same format as lfq_protein
# we can still use the phenoData for lfq_protein to define the condition and
# replicate vectors, so long as we re-order lfq_protein using the exprs matrix
# column names first
treatment <- pData(lfq_protein[,colnames(filtered_exprs_lna)])$Condition
treatment <- factor(treatment, levels = c('CL', 'NCL'))

limma_design_lna <- model.matrix(formula(~treatment))

limma_fit_lna <- lmFit(filtered_exprs_lna, limma_design_lna)
limma_fit_lna <- eBayes(limma_fit_lna, trend=TRUE)

limma::plotSA(limma_fit_lna)


# The next two steps could be a function since they will always be the same
# get_min_peptides <- function(filtered_wide_expr) {}
filtered_lfq_protein_long_lna <- filtered_exprs_lna %>%
  data.frame() %>%
  tibble::rownames_to_column('Master.Protein.Accessions') %>%
  pivot_longer(cols=-Master.Protein.Accessions, values_to='abundance', names_to='sample') %>%
  filter(is.finite(abundance)) # We only want to consider samples with a ratio quantified

min_pep_count_lna <- camprotR::count_features_per_protein(pep) %>%
  merge(filtered_lfq_protein_long_lna, by=c('Master.Protein.Accessions', 'sample')) %>%
  
  group_by(Master.Protein.Accessions) %>%
  summarise(min_pep_count = min(n))
###


# add the min peptide count
limma_fit_lna$count <- min_pep_count_lna$min_pep_count

efit_deqms_lna <- suppressWarnings(spectraCounteBayes(limma_fit_lna))

VarianceBoxplot(efit_deqms_lna, n = 30, xlab = "Peptides")


deqms_results_lna <- outputResult(efit_deqms_lna, coef_col=2)
hist(deqms_results_lna$P.Value)

hist(deqms_results_lna$sca.P.Value)


table(deqms_results_lna$sca.adj.pva<0.1)

FALSE 
  174 
deqms_results_lna %>% filter(logFC>0) %>% head()

deqms_results_lna %>%
  ggplot(aes(x = logFC, y = -log10(sca.P.Value), colour = sca.adj.pval < 0.1)) +
  geom_point() +
  theme_camprot(border=FALSE, base_size=15) +
  scale_colour_manual(values = c('grey', get_cat_palette(2)[2]), name = 'CL vs NCL Sig.') +
  labs(x = 'LNA CL/NCL', y = '-log10(p-value)')

———————Test ION CL vs NCL data————————–


lfq_protein_tidy_ion <- lfq_protein %>%
  # make the MSnSet into 'tidy' format for further testing
  biobroom::tidy.MSnSet(addPheno=TRUE) %>% # addPheno=TRUE adds the phenotype columns
  subset(sample=="NCL_ION_1"| sample=="NCL_ION_2" | sample=="NCL_ION_3" | sample=="CL_ION_1"| sample=="CL_ION_2" | sample=="CL_ION_3") %>%
  # TS: would be best to have a column in the pData which describes the VEH/LNA/ION variable, so you can just filter using that column
  subset(sample %in% c("NCL_ION_1", "NCL_ION_2", "NCL_ION_3", "CL_ION_1", "CL_ION_2", "CL_ION_3")) %>%
  filter(is.finite(value)) %>%
  group_by(protein, Condition) %>%
  filter(n()>=2) %>% 
  group_by(protein) %>%
  filter(length(unique(Condition))==2) %>%# n() is the length of the group
  ungroup()

  
# This could be a function since it always be the same steps
# make_expr_wide <- function(tidy_expr) {}
filtered_exprs_ion <- lfq_protein_tidy_ion %>%
  pivot_wider(names_from=sample, values_from=value, id_cols=protein) %>%
  tibble::column_to_rownames('protein') %>%
  as.matrix()

# Since the column names for our filtered exprs matrix is in the same format as lfq_protein
# we can still use the phenoData for lfq_protein to define the condition and
# replicate vectors, so long as we re-order lfq_protein using the exprs matrix
# column names first
treatment <- pData(lfq_protein[,colnames(filtered_exprs_ion)])$Condition
treatment <- factor(treatment, levels = c('CL', 'NCL'))

limma_design_ion <- model.matrix(formula(~treatment))

limma_fit_ion <- lmFit(filtered_exprs_ion, limma_design_ion)
limma_fit_ion <- eBayes(limma_fit_ion, trend=TRUE)

limma::plotSA(limma_fit_ion)


# The next two steps could be a function since they will always be the same
# get_min_peptides <- function(filtered_wide_expr) {}
filtered_lfq_protein_long_ion <- filtered_exprs_ion %>%
  data.frame() %>%
  tibble::rownames_to_column('Master.Protein.Accessions') %>%
  pivot_longer(cols=-Master.Protein.Accessions, values_to='abundance', names_to='sample') %>%
  filter(is.finite(abundance)) # We only want to consider samples with a ratio quantified

min_pep_count_ion <- camprotR::count_features_per_protein(pep) %>%
  merge(filtered_lfq_protein_long_ion, by=c('Master.Protein.Accessions', 'sample')) %>%
  
  group_by(Master.Protein.Accessions) %>%
  summarise(min_pep_count = min(n))
###


# add the min peptide count
limma_fit_ion$count <- min_pep_count_ion$min_pep_count

efit_deqms_ion <- suppressWarnings(spectraCounteBayes(limma_fit_ion))

VarianceBoxplot(efit_deqms_ion, n = 30, xlab = "Peptides")


deqms_results_ion <- outputResult(efit_deqms_ion, coef_col=2)
hist(deqms_results_ion$P.Value)

hist(deqms_results_ion$sca.P.Value)


table(deqms_results_ion$sca.adj.pva<0.1)

FALSE 
  110 
deqms_results_ion %>% filter(logFC>0) %>% head()

deqms_results_ion %>%
  ggplot(aes(x = logFC, y = -log10(sca.P.Value), colour = sca.adj.pval < 0.1)) +
  geom_point() +
  theme_camprot(border=FALSE, base_size=15) +
  scale_colour_manual(values = c('grey', get_cat_palette(2)[2]), name = 'CL vs NCL Sig.') +
  labs(x = 'ION CL/NCL', y = '-log10(p-value)')

———————Test VEH CL vs NCL data————————–


lfq_protein_tidy_veh <- lfq_protein %>%
  # make the MSnSet into 'tidy' format for further testing
  biobroom::tidy.MSnSet(addPheno=TRUE) %>% # addPheno=TRUE adds the phenotype columns
  subset(sample=="NCL_VEH_1"| sample=="NCL_VEH_2" | sample=="NCL_VEH_3" | sample=="CL_VEH_1"| sample=="CL_VEH_2" | sample=="CL_VEH_3") %>%
  filter(is.finite(value)) %>%
  group_by(protein, Condition) %>%
  filter(n()>=2) %>% 
  group_by(protein) %>%
  filter(length(unique(Condition))==2) %>%# n() is the length of the group
  ungroup()

  
# This could be a function since it always be the same steps
# make_expr_wide <- function(tidy_expr) {}
filtered_exprs_veh <- lfq_protein_tidy_veh %>%
  pivot_wider(names_from=sample, values_from=value, id_cols=protein) %>%
  tibble::column_to_rownames('protein') %>%
  as.matrix()

# Since the column names for our filtered exprs matrix is in the same format as lfq_protein
# we can still use the phenoData for lfq_protein to define the condition and
# replicate vectors, so long as we re-order lfq_protein using the exprs matrix
# column names first
treatment <- pData(lfq_protein[,colnames(filtered_exprs_veh)])$Condition
treatment <- factor(treatment, levels = c('CL', 'NCL'))

limma_design_veh <- model.matrix(formula(~treatment))

limma_fit_veh <- lmFit(filtered_exprs_veh, limma_design_veh)
limma_fit_veh <- eBayes(limma_fit_veh, trend=TRUE)

limma::plotSA(limma_fit_veh)


# The next two steps could be a function since they will always be the same
# get_min_peptides <- function(filtered_wide_expr) {}
filtered_lfq_protein_long_veh <- filtered_exprs_veh %>%
  data.frame() %>%
  tibble::rownames_to_column('Master.Protein.Accessions') %>%
  pivot_longer(cols=-Master.Protein.Accessions, values_to='abundance', names_to='sample') %>%
  filter(is.finite(abundance)) # We only want to consider samples with a ratio quantified

min_pep_count_veh <- camprotR::count_features_per_protein(pep) %>%
  merge(filtered_lfq_protein_long_veh, by=c('Master.Protein.Accessions', 'sample')) %>%
  
  group_by(Master.Protein.Accessions) %>%
  summarise(min_pep_count = min(n))
###


# add the min peptide count
limma_fit_veh$count <- min_pep_count_veh$min_pep_count

efit_deqms_veh <- suppressWarnings(spectraCounteBayes(limma_fit_veh))

VarianceBoxplot(efit_deqms_veh, n = 30, xlab = "Peptides")


deqms_results_veh <- outputResult(efit_deqms_veh, coef_col=2)
hist(deqms_results_veh$P.Value)

hist(deqms_results_veh$sca.P.Value)


table(deqms_results_veh$sca.adj.pva<0.1)

FALSE 
  112 
deqms_results_veh %>% filter(logFC>0) %>% head()

deqms_results_veh %>%
  ggplot(aes(x = logFC, y = -log10(sca.P.Value), colour = sca.adj.pval < 0.1)) +
  geom_point() +
  theme_camprot(border=FALSE, base_size=15) +
  scale_colour_manual(values = c('grey', get_cat_palette(2)[2]), name = 'CL vs NCL Sig.') +
  labs(x = 'VEH_CL/NCL', y = '-log10(p-value)')

———————Test ION CL vs LNA CL data————————–

Let’s visualise the abundance distributions of the samples in question.

plot_quant(lfq_protein[,grepl('^CL_(ION|LNA)_\\d', colnames(lfq_protein))], method='density') +
  scale_colour_manual(values=rep(get_cat_palette(2), each=3))

plot_quant(lfq_protein[,grepl('^CL_(ION|LNA)_\\d', colnames(lfq_protein))], method='box')

lfq_protein_tidy_ION_LNA <- lfq_protein %>%
  # make the MSnSet into 'tidy' format for further testing
  biobroom::tidy.MSnSet(addPheno=TRUE) %>% # addPheno=TRUE adds the phenotype columns
  filter(grepl('^CL_(ION|LNA)_\\d', sample)) %>%
  separate(sample, into=c(NA, 'Treatment', NA), remove = FALSE) %>%
  filter(is.finite(value)) %>%
  group_by(protein) %>%
  filter(n()>=2) %>% 
  group_by(protein) %>%
  filter(length(unique(Treatment))==2) %>%# n() is the length of the group
  ungroup()

  
# This could be a function since it always be the same steps
# make_expr_wide <- function(tidy_expr) {}
filtered_exprs_ion_lna_cl <- lfq_protein_tidy_ION_LNA %>%
  pivot_wider(names_from=sample, values_from=value, id_cols=protein) %>%
  tibble::column_to_rownames('protein') %>%
  as.matrix()

# Since the column names for our filtered exprs matrix is in the same format as lfq_protein
# we can still use the phenoData for lfq_protein to define the condition and
# replicate vectors, so long as we re-order lfq_protein using the exprs matrix
# column names first

library(tibble)

pheno_data2<- rownames_to_column(pData(lfq_protein[,colnames(filtered_exprs_ion_lna_cl)]), var = "sample")
pheno_data2 <- pheno_data2 %>% separate(sample, into=c(NA, 'Treatment', NA), remove = FALSE) 

treatment2 <- pheno_data2$Treatment
treatment2 <- factor(treatment2, levels = c('ION', 'LNA'))
replicate <- pheno_data2$Replicate

limma_design_ion_lna_cl <- model.matrix(formula(~replicate+treatment2))

limma_fit_ion_lna_cl <- lmFit(filtered_exprs_ion_lna_cl, limma_design_ion_lna_cl)
Warning: Partial NA coefficients for 35 probe(s)
limma_fit_ion_lna_cl <- eBayes(limma_fit_ion_lna_cl, trend=TRUE)

limma::plotSA(limma_fit_ion_lna_cl)


# The next two steps could be a function since they will always be the same
# get_min_peptides <- function(filtered_wide_expr) {}
filtered_lfq_protein_long_ion_lna_cl <- filtered_exprs_ion_lna_cl %>%
  data.frame() %>%
  tibble::rownames_to_column('Master.Protein.Accessions') %>%
  pivot_longer(cols=-Master.Protein.Accessions, values_to='abundance', names_to='sample') %>%
  filter(is.finite(abundance)) # We only want to consider samples with a ratio quantified

min_pep_count_ion_lna_cl <- camprotR::count_features_per_protein(pep) %>%
  merge(filtered_lfq_protein_long_ion_lna_cl, by=c('Master.Protein.Accessions', 'sample')) %>%
  
  group_by(Master.Protein.Accessions) %>%
  summarise(min_pep_count = min(n))
###


# add the min peptide count
limma_fit_ion_lna_cl$count <- min_pep_count_ion_lna_cl$min_pep_count

efit_deqms_ion_lna_cl <- suppressWarnings(spectraCounteBayes(limma_fit_ion_lna_cl))

VarianceBoxplot(efit_deqms_ion_lna_cl, n = 30, xlab = "Peptides")


head(coefficients(efit_deqms_ion_lna_cl))
       (Intercept)  replicate treatment2LNA
E9PRG8   18.518541 -0.2047557    -0.6418709
O00567   17.244731  0.6021092    -2.1276393
O00763   14.560631  0.7504844     0.4756514
O14818   14.621599  0.6324806     1.6847410
O60832   18.193577  0.5491689    -1.4823259
O75083    7.997309  5.4577333    -4.2756229
deqms_results_ion_lna_cl <- outputResult(efit_deqms_ion_lna_cl, coef_col=3)
hist(deqms_results_ion_lna_cl$P.Value)

hist(deqms_results_ion_lna_cl$sca.P.Value)


table(deqms_results_ion_lna_cl$sca.adj.pva<0.1)

FALSE 
  194 
deqms_results_ion_lna_cl %>% filter(logFC>0) %>% head()

deqms_results_veh %>%
  ggplot(aes(x = logFC, y = -log10(sca.P.Value), colour = sca.adj.pval < 0.1)) +
  geom_point() +
  theme_camprot(border=FALSE, base_size=15) +
  scale_colour_manual(values = c('grey', get_cat_palette(2)[2]), name = 'ION vs LNA Sig.') +
  labs(x = 'IonCL vs LNACL (Log2)', y = '-log10(p-value)')


———————LNA v VEH————————–


lfq_protein_tidy_LNA_VEH <- lfq_protein %>%
  # make the MSnSet into 'tidy' format for further testing
  biobroom::tidy.MSnSet(addPheno=TRUE) %>% # addPheno=TRUE adds the phenotype columns
  filter(grepl('^CL_(LNA|VEH)_\\d', sample)) %>%
  separate(sample, into=c(NA, 'Treatment', NA), remove = FALSE) %>%
  filter(is.finite(value)) %>%
  group_by(protein) %>%
  filter(n()>=2) %>% 
  group_by(protein) %>%
  filter(length(unique(Treatment))==2) %>%# n() is the length of the group
  ungroup()

  
# This could be a function since it always be the same steps
# make_expr_wide <- function(tidy_expr) {}
filtered_exprs_lna_veh_cl <- lfq_protein_tidy_LNA_VEH %>%
  pivot_wider(names_from=sample, values_from=value, id_cols=protein) %>%
  tibble::column_to_rownames('protein') %>%
  as.matrix()

# Since the column names for our filtered exprs matrix is in the same format as lfq_protein
# we can still use the phenoData for lfq_protein to define the condition and
# replicate vectors, so long as we re-order lfq_protein using the exprs matrix
# column names first

library(tibble)

pheno_data2<- rownames_to_column(pData(lfq_protein[,colnames(filtered_exprs_lna_veh_cl)]), var = "sample")
pheno_data2 <- pheno_data2 %>% separate(sample, into=c(NA, 'Treatment', NA), remove = FALSE) 

treatment2 <- pheno_data2$Treatment
treatment2 <- factor(treatment2, levels = c('LNA', 'VEH'))
replicate <- pheno_data2$Replicate

limma_design_lna_veh_cl <- model.matrix(formula(~replicate+treatment2))

limma_fit_lna_veh_cl <- lmFit(filtered_exprs_lna_veh_cl, limma_design_lna_veh_cl)
Warning: Partial NA coefficients for 8 probe(s)
limma_fit_lna_veh_cl <- eBayes(limma_fit_lna_veh_cl, trend=TRUE)

limma::plotSA(limma_fit_lna_veh_cl)


# The next two steps could be a function since they will always be the same
# get_min_peptides <- function(filtered_wide_expr) {}
filtered_lfq_protein_long_lna_veh_cl <- filtered_exprs_lna_veh_cl %>%
  data.frame() %>%
  tibble::rownames_to_column('Master.Protein.Accessions') %>%
  pivot_longer(cols=-Master.Protein.Accessions, values_to='abundance', names_to='sample') %>%
  filter(is.finite(abundance)) # We only want to consider samples with a ratio quantified

min_pep_count_lna_veh_cl <- camprotR::count_features_per_protein(pep) %>%
  merge(filtered_lfq_protein_long_lna_veh_cl, by=c('Master.Protein.Accessions', 'sample')) %>%
  
  group_by(Master.Protein.Accessions) %>%
  summarise(min_pep_count = min(n))
###


# add the min peptide count
limma_fit_lna_veh_cl$count <- min_pep_count_lna_veh_cl$min_pep_count

efit_deqms_lna_veh_cl <- suppressWarnings(spectraCounteBayes(limma_fit_lna_veh_cl))

VarianceBoxplot(efit_deqms_lna_veh_cl, n = 30, xlab = "Peptides")


head(coefficients(efit_deqms_lna_veh_cl))
       (Intercept)  replicate treatment2VEH
E9PRG8    19.19580 -0.8643230     2.7197521
O00567    16.69525 -0.1869708     0.8729443
O00763    16.05610  0.2405753     1.0391871
O60832    18.11754 -0.1539763    -1.1314673
O75223    18.12962 -0.9791181     0.9090755
O75683    18.45775 -1.2348835     1.7131505
deqms_results_lna_veh_cl <- outputResult(efit_deqms_lna_veh_cl, coef_col=3)
hist(deqms_results_lna_veh_cl$P.Value)

hist(deqms_results_lna_veh_cl$sca.P.Value)

table(deqms_results_lna_veh_cl$sca.adj.pval<0.1)

FALSE 
  229 

———————ION_CLv VEH_CL————————–

Let’s visualise the abundance distributions of the samples in question.

plot_quant(lfq_protein[,grepl('^CL_(ION|VEH)_\\d', colnames(lfq_protein))], method='density') +
  scale_colour_manual(values=rep(get_cat_palette(2), each=3))

plot_quant(lfq_protein[,grepl('^CL_(ION|VEH)_\\d', colnames(lfq_protein))], method='box')


lfq_protein_tidy_ION_VEH <- lfq_protein %>%
  # make the MSnSet into 'tidy' format for further testing
  biobroom::tidy.MSnSet(addPheno=TRUE) %>% # addPheno=TRUE adds the phenotype columns
  filter(grepl('^CL_(ION|VEH)_\\d', sample)) %>%
  separate(sample, into=c(NA, 'Treatment', NA), remove = FALSE) %>%
  filter(is.finite(value)) %>%
  group_by(protein) %>%
  filter(n()>=2) %>% 
  group_by(protein) %>%
  filter(length(unique(Treatment))==2) %>%# n() is the length of the group
  ungroup()

  
# This could be a function since it always be the same steps
# make_expr_wide <- function(tidy_expr) {}
filtered_exprs_ion_veh_cl <- lfq_protein_tidy_ION_VEH %>%
  pivot_wider(names_from=sample, values_from=value, id_cols=protein) %>%
  tibble::column_to_rownames('protein') %>%
  as.matrix()

# Since the column names for our filtered exprs matrix is in the same format as lfq_protein
# we can still use the phenoData for lfq_protein to define the condition and
# replicate vectors, so long as we re-order lfq_protein using the exprs matrix
# column names first

library(tibble)

pheno_data2<- rownames_to_column(pData(lfq_protein[,colnames(filtered_exprs_ion_veh_cl)]), var = "sample")
pheno_data2 <- pheno_data2 %>% separate(sample, into=c(NA, 'Treatment', NA), remove = FALSE) 

treatment2 <- pheno_data2$Treatment
treatment2 <- factor(treatment2, levels = c('VEH', 'ION'))
replicate <- pheno_data2$Replicate

limma_design_ion_veh_cl <- model.matrix(formula(~replicate+treatment2))

limma_fit_ion_veh_cl <- lmFit(filtered_exprs_ion_veh_cl, limma_design_ion_veh_cl)
Warning: Partial NA coefficients for 4 probe(s)
limma_fit_ion_veh_cl <- eBayes(limma_fit_ion_veh_cl, trend=TRUE)

limma::plotSA(limma_fit_ion_veh_cl)


# The next two steps could be a function since they will always be the same
# get_min_peptides <- function(filtered_wide_expr) {}
filtered_lfq_protein_long_ion_veh_cl <- filtered_exprs_ion_veh_cl %>%
  data.frame() %>%
  tibble::rownames_to_column('Master.Protein.Accessions') %>%
  pivot_longer(cols=-Master.Protein.Accessions, values_to='abundance', names_to='sample') %>%
  filter(is.finite(abundance)) # We only want to consider samples with a ratio quantified

min_pep_count_ion_veh_cl <- camprotR::count_features_per_protein(pep) %>%
  merge(filtered_lfq_protein_long_ion_veh_cl, by=c('Master.Protein.Accessions', 'sample')) %>%
  
  group_by(Master.Protein.Accessions) %>%
  summarise(min_pep_count = min(n))
###


# add the min peptide count
limma_fit_ion_veh_cl$count <- min_pep_count_ion_veh_cl$min_pep_count

efit_deqms_ion_veh_cl <- suppressWarnings(spectraCounteBayes(limma_fit_ion_veh_cl))

VarianceBoxplot(efit_deqms_ion_veh_cl, n = 30, xlab = "Peptides")


head(coefficients(efit_deqms_ion_veh_cl))
       (Intercept)   replicate treatment2ION
E9PRG8    20.31379 -0.06343931     -2.077881
O00567    15.11866  1.03779547      1.254695
O00763    16.19642  0.69000834     -1.514839
O14818    16.98582  0.24715110     -1.593563
O60832    15.03535  0.82138624      2.613793
O75083    13.96638  0.49509075      1.474893
deqms_results_ion_veh_cl <- outputResult(efit_deqms_ion_veh_cl, coef_col=3)
hist(deqms_results_ion_veh_cl$P.Value)

hist(deqms_results_ion_veh_cl$sca.P.Value)

p_threshold <- 0.1
table(deqms_results_ion_veh_cl$sca.adj.pva<p_threshold)

FALSE  TRUE 
  249     1 
deqms_results_ion_veh_cl %>% head()

deqms_results_ion_veh_cl %>%
  tibble::rownames_to_column('uniprotID') %>%
  ggplot(aes(x = logFC, y = -log10(sca.P.Value), colour = sca.adj.pval < p_threshold)) +
  geom_point() +
  ggrepel::geom_text_repel(aes(label=ifelse(sca.adj.pval<p_threshold, uniprotID, NA)), 
                           show.legend=FALSE) +
  theme_camprot(border=FALSE, base_size=15) +
  scale_colour_manual(values = c('grey', get_cat_palette(2)[2]), name = 'ION vs VEH Sig.') +
  labs(x = 'IONCL vs VEHCL (Log2)', y = '-log10(p-value)')

Checking protein level abundance for P67809 (YBX1) and P19338 (NCL)

LS0tCnRpdGxlOiAiMjAyNF8wMl8yNl9BU09fQmlvdGluX3B1bGxkb3duX0RhdGFfcHJvY2Vzc2luZ19naXQiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoTVNuYmFzZSkKbGlicmFyeShiaW9icm9vbSkKbGlicmFyeShjYW1wcm90UikKbGlicmFyeShQcm90ZW9taWNzLmFuYWx5c2lzLmRhdGEpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoaGVyZSkKbGlicmFyeShERXFNUykKbGlicmFyeShsaW1tYSkKbGlicmFyeShicm9vbSkKCgpgYGAKCklucHV0IGRhdGEuCgoqV2UnbGwgc3RhcnQgYnkgcmVhZGluZyBpbiB0aGUgZGF0YSB0byBhIGRhdGEuZnJhbWUsIHdoaWNoIGlzIGEgZ2VuZXJpYyBkYXRhLnN0cnVjdHVyZSwgc3VpdGFibGUgZm9yIGFueSBkYXRhIHRhYmxlLiBUaGlzIGlzIHRoZSByZXF1aXJlZCBpbnB1dCBmb3IgYENhbXByb3RSOjpwYXJzZV9mZWF0dXJlc2AgYW5kIGFsbG93cyB1cyB0byBwZXJmb3JtIG1hbnVhbCBmaWx0ZXJpbmcgd2l0aCBlLmcgYGRwbHlyYCBlYXNpbHkqCgpgYGB7cn0KcGVwX2RhdGEgPC0gcmVhZC5kZWxpbShoZXJlKCIyMDI0XzAyXzEzX1BlcHRpZGVfZGF0YS50eHQiKSkKCmBgYAoKZmlsdGVyIGNyYXAgcHJvdGVpbnMKCmBgYHtyfQpjcmFwX2Zhc3RhX2luZiA8LWhlcmUoJzIwMjNfMDJfQ0NQX2NSQVAuZmFzdGEuZ3onKQoKIyBMb2FkIHRoZSBjUkFQIEZBU1RBIHVzZWQgZm9yIHRoZSBQRCBzZWFyY2gKY3JhcF9mYXN0YSA8LSBCaW9zdHJpbmdzOjpmYXN0YS5pbmRleChjcmFwX2Zhc3RhX2luZiwgc2VxdHlwZSA9ICJBQSIpCgojIEV4dHJhY3QgdGhlIFVuaVByb3QgYWNjZXNzaW9ucyBhc3NvY2lhdGVkIHdpdGggZWFjaCBjUkFQIHByb3RlaW4KY3JhcF9hY2Nlc3Npb25zIDwtIGNyYXBfZmFzdGEgJT4lCiAgcHVsbChkZXNjKSAlPiUKICBzdHJpbmdyOjpzdHJfZXh0cmFjdF9hbGwocGF0dGVybj0iKD88PVxcfCkuKj8oPz1cXHwpIikgJT4lCiAgdW5saXN0KCkKYGBgCgpgYGB7cn0KcGVwX2RhdGFfZmx0IDwtIGNhbXByb3RSOjpwYXJzZV9mZWF0dXJlcygKICBwZXBfZGF0YSwKICBsZXZlbCA9ICdwZXB0aWRlJywKICBjcmFwX3Byb3RlaW5zID0gY3JhcF9hY2Nlc3Npb25zLAogIHVuaXF1ZV9tYXN0ZXIgPSBGQUxTRQopCgoKI2xldmVsID0gJ3BlcHRpZGUnOiBUaGlzIHBhcmFtZXRlciBzcGVjaWZpZXMgdGhhdCB5b3UncmUgaW50ZXJlc3RlZCBpbiBwYXJzaW5nIHBlcHRpZGUtbGV2ZWwgZmVhdHVyZXMgZnJvbSBwZXBfZGF0YS4KI2NyYXBfcHJvdGVpbnMgPSBjcmFwX2FjY2Vzc2lvbnM6IFRoaXMgcGFyYW1ldGVyIHNlZW1zIHRvIHNwZWNpZnkgYSB2ZWN0b3IgKGNyYXBfYWNjZXNzaW9ucykgY29udGFpbmluZyBwcm90ZWluIGFjY2Vzc2lvbnMgdGhhdCBhcmUgY29uc2lkZXJlZCBjb250YW1pbmFudHMgb3IgaXJyZWxldmFudCBmb3IgeW91ciBhbmFseXNpcy4gVGhlIGZ1bmN0aW9uIGxpa2VseSBmaWx0ZXJzIG91dCBwZXB0aWRlcyBhc3NvY2lhdGVkIHdpdGggdGhlc2UgcHJvdGVpbnMuCmBgYAoKRmlsdGVyIGZvciB1bmlxdWUgbWFzdGVyIHByb3RlaW5zCgpgYGB7cn0KcGVwX2RhdGFfZmx0IDwtIHBlcF9kYXRhX2ZsdCAlPiUgZmlsdGVyKCFncmVwbCgnOycsIE1hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMpKQpjYW1wcm90Ujo6Om1lc3NhZ2VfcGFyc2UocGVwX2RhdGFfZmx0LCAnTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucycsICJmZWF0dXJlcyB3aXRoIG5vbi11bmlxdWUgbWFzdGVyIHByb3RlaW5zIHJlbW92ZWQiKQpgYGAKCmBgYHtyfQpzYW1wbGVfZGF0YSA8LXJlYWQuY3N2KCJTYW1wbGVfZGF0YS5jc3YiKQoKIyBEaXNwbGF5aW5nIHRoZSB0YWJsZSBpbiBhIG5pY2VyIGZvcm1hdAprbml0cjo6a2FibGUoc2FtcGxlX2RhdGEsCiAgICAgICAgICAgICBhbGlnbiA9ICJjY2NjIiwKICAgICAgICAgICAgIGZvcm1hdCA9ICJodG1sIiwKICAgICAgICAgICAgIHRhYmxlLmF0dHIgPSAic3R5bGU9J3dpZHRoOjMwJTsnIikKYGBgCgpDb252ZXJ0IHRvIGFuIE1zblNldAoKKkEgYE1zblNldGAgaXMgYSBwcm90ZW9taWNzLXNwZWNpZmljIGRhdGEgc3RydWN0dXJlLCB3aXRoIGZ1bmN0aW9ucyBhdmFpbGFibGUgZm9yIHN0YW5kYXJkIHByb3Rlb21pY3Mgd29ya2Zsb3dzLiBJdCdzIHNpbmNlIGJlZW4gZGVwcmVjYXRlZCBhbmQgcmVwbGFjZWQgYnkgYFFmZWF0dXJlc2AsIHdoaWNoIGFsbG93cyBtdWx0aXBsZSBsZXZlbHMgb2YgcXVhbnRpZmljYXRpb24gdG8gYmUgc3RvcmVkIGluIHRoZSBzYW1lIG9iamVjdCwgd2l0aCBkZXRhaWxzIGFib3V0IGhvdyB0aGUgZmVhdHVyZXMgaW4gZWFjaCBsZXZlbCBhcmUgbGlua2VkLiBOb25ldGhlbGVzcywgYSBgTXNuU2V0YCBpcyBzdGlsbCBhIHNlbnNpYmxlIGRhdGEgc3RydWN0dXJlIHRvIHVzZSBmb3IgcHJvdGVvbWljcyBkYXRhLiBNYW55IGBjYW1wcm90UmAgZnVuY3Rpb25zIGhhdmUgYmVlbiB3cml0dGVuIGV4cGxpY2l0bHkgdG8gd29yayB3aXRoIGBNc25TZXRzYCwgZm9yIGV4YW1wbGUgYHBsb3RfcXVhbnRgIGluIHRoZSBjb2RlIGJsb2NrIGFmdGVyIHRoaXMuKgoKPQoKYGBge3J9CiNzZWxlY3QgY29sdW1ucyB3aXRoIHRoZSB3b3JkICJhYnVuZGFuY2UiIGluLgpleHByc19kYXRhIDwtIHBlcF9kYXRhX2ZsdCAlPiUKICBzZWxlY3QobWF0Y2hlcygiQWJ1bmRhbmNlLi5GLioiKSkgJT4lCiAgYXMubWF0cml4KCkKCiMgY2hlY2sgdGhlIG9yZGVyIG9mIHRoZSBzYW1wbGVzLiBUaGV5IGFwcGVhciB0byBiZSBvcmRlcmVkIGFzIHJlcDEtMyBmb3IgZWFjaCBjb25kaXRpb24uCiMgSWYgdGhlIHJlcGxpY2F0ZSBkZXRhaWwgaXMgaW1wb3J0YW50LCBlLmcgcGVyZm9ybWVkIG9uIHNlcGFyYXRlIGRheXMsIGl0IHdvdWxkIGJlCiMgcHJ1ZGVudCB0byBjaGVjayB3aXRoIFNhbmRpcCBpZiB0aGV5IHdlcmUgaW5kZWVkIGFsbCBydW4gaW4gb3JkZXIgcmVwMS0zLgojIEhlIHNob3VsZCBoYXZlIHNoYXJlZCBhbiBJbnB1dEZpbGVzLnR4dCBmaWxlIHdoaWNoIHdpbGwgc3BlY2lmeSB0aGUgZnVsbCBzYW1wbGUgbmFtZSBmb3IgZWFjaCBydW4sCiMgd2hpY2ggd291bGQgaG9wZWZ1bGx5IGRldGFpbCB0aGUgcmVwbGljYXRlIG51bWJlcgpwcmludChjb2xuYW1lcyhleHByc19kYXRhKSkKCiMgUmVtb3ZlIHRoZSB1bndhbnRlZCBwYXJ0IG9mIHRoZSBjb2x1bW4gbmFtZQpjb2xuYW1lcyhleHByc19kYXRhKSA8LSBnc3ViKCdBYnVuZGFuY2UuLi4qLi5TYW1wbGUuLicsICcnLCBjb2xuYW1lcyhleHByc19kYXRhKSkKCiMgQWRkIHRoZSByZXBsaWNhdGUgbnVtYmVyCmNvbG5hbWVzKGV4cHJzX2RhdGEpIDwtIHBhc3RlKGNvbG5hbWVzKGV4cHJzX2RhdGEpLCByZXAoYygxLDIsMyksIDYpLCBzZXA9J18nKQoKIyBDcmVhdGUgZGF0YS5mcmFtZSB3aXRoIHNhbXBsZSBtZXRhZGF0YSAocERhdGEpCiN0YWtlcyBzYW1wbGUgZGF0YSBhbmQgc2VsZWN0cyBldmVyeSBjb2x1bW4gd2l0aG91dCAiZmlsZSIgaW4gdGhlIG5hbWUsIHRoZW4gY3JlYXRlcyBhIHRpYmJsZSB3aXRoIHRoZSByb3duYW1lcyBhcyB0aGUgY29sdW1uICJzYW1wbGUiCnBoZW5vX2RhdGEgPC0gc2FtcGxlX2RhdGEgJT4lCiAgc2VsZWN0KC1GaWxlKSAlPiUKICB0aWJibGU6OmNvbHVtbl90b19yb3duYW1lcyh2YXIgPSAiU2FtcGxlIikKCgojIFJlb3JkZXIgdGhlIHBoZW5vdHlwZSBkYXRhIHRvIGJlIGluIHRoZSBzYW1lIG9yZGVyIGFzIHRoZSBleHBycyBtYXRyaXgKIyBFeGFtcGxlIGRhdGFmcmFtZQoKIyBEZWZpbmUgdGhlIGRlc2lyZWQgb3JkZXIgb2YgY29sdW1ucwpkZXNpcmVkX29yZGVyIDwtIGMoIkNMX0lPTl8xIiwiQ0xfSU9OXzIiLCJDTF9JT05fMyIsICJDTF9MTkFfMSIsIkNMX0xOQV8yIiwiQ0xfTE5BXzMiLCAiQ0xfVkVIXzEiLCJDTF9WRUhfMiIsIkNMX1ZFSF8zIiwiTkNMX0lPTl8xIiwiTkNMX0lPTl8yIiwiTkNMX0lPTl8zIiwgIk5DTF9MTkFfMSIsIk5DTF9MTkFfMiIsIk5DTF9MTkFfMyIsICJOQ0xfVkVIXzEiLCJOQ0xfVkVIXzIiLCJOQ0xfVkVIXzMiKQoKIyBTdWJzZXQgdGhlIGRhdGFmcmFtZSB3aXRoIHRoZSBkZXNpcmVkIGNvbHVtbiBvcmRlcgpwaGVub19kYXRhIDwtIHBoZW5vX2RhdGFbZGVzaXJlZF9vcmRlcixdCgoKCiMgQ3JlYXRlIGRhdGEuZnJhbWUgd2l0aCBwZXB0aWRlIG1ldGFkYXRhIChmRGF0YSkKIyBzZWxlY3QgYWxsIGNvbHVtbnMgZXhjZXB0IHRob3NlIHdpdGggYWJ1bmRhbmNlIGluIHRoZSBuYW1lLgpmZWF0dXJlX2RhdGEgPC0gcGVwX2RhdGFfZmx0ICU+JQogICMgVGhpcyByZXRhaW5zIGFsbCBjb2x1bW5zIGV4Y2VwdCB0aGUgYWJ1bmRhbmNlIGNvbHVtbnMuIFdlIGRvbid0IHJlYWxseSBuZWVkIG1vc3QgY29sdW1ucwogICNzZWxlY3QoLW1hdGNoZXMoIkFidW5kYW5jZSIpKSAKICAjIFRoaXMganVzdCByZXRhaW5zIHRoZSB1c2VmdWwgY29sdW1ucwogIHNlbGVjdChBbm5vdGF0ZWQuU2VxdWVuY2UsIE1vZGlmaWNhdGlvbnMsIFByb3RlaW4uQWNjZXNzaW9ucywgTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucykKCgojIENyZWF0ZSBNU25TZXQKcGVwIDwtIE1TbmJhc2U6Ok1TblNldChleHBycyA9IGV4cHJzX2RhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgZkRhdGEgPSBmZWF0dXJlX2RhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgcERhdGEgPSBwaGVub19kYXRhKQoKCmBgYAoKCkxldCdzIFFDIHRoZSBwZXB0aWRlcwoKYGBge3J9CnBlcCAlPiUKICBsb2coYmFzZSA9IDIpICU+JQogIGNhbXByb3RSOjpwbG90X3F1YW50KG1ldGhvZCA9ICdib3gnKQpgYGAKCmxldCdzIGxvb2sgYXQgc29tZSBjb29sIHBlcHRpZGUgaW50ZW5zaXRpZXMKCmBgYHtyfQpwZXAgJT4lCiAgbG9nKGJhc2UgPSAyKSAlPiUKICBjYW1wcm90Ujo6cGxvdF9xdWFudChtZXRob2QgPSAnZGVuc2l0eScpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1yZXAoZ2V0X2NhdF9wYWxldHRlKDYpLCBlYWNoPTMpKQpgYGAKCmBgYHtyfQpwIDwtIE1TbmJhc2U6OnBsb3ROQShwZXAsIHBOQSA9IDApICsKICBjYW1wcm90Ujo6dGhlbWVfY2FtcHJvdChib3JkZXIgPSBGQUxTRSwgYmFzZV9mYW1pbHkgPSAnc2FucycsIGJhc2Vfc2l6ZSA9IDEwKSArCiAgbGFicyh4ID0gJ1BlcHRpZGUgaW5kZXgnKQpgYGAKCipuYW5pYXI6OmdnX21pc3NfdXBzZXQgaXMgYSBnZW5lcmljIGZ1bmN0aW9uLCBub3Qgb25lIHNwZWNpZmljYWxseSBmb3IgcHJvdGVvbWljcyBkYXRhLiBBcyBzdWNoLCBpdCB3YXMgZGVzaWduZWQgdG8gdGFrZSBhIGRhdGEuZnJhbWUoKSB3aXRoIGp1c3QgbnVtZXJpYyBjb2x1bW5zLiBIZW5jZSB3ZSBleHRyYWN0IHRoZSBleHBycyBtYXRyaXggZnJvbSB0aGUgTXNuU2V0IGFuZCBjb252ZXJ0IHRvIGEgZGF0YS5mcmFtZSoKCmBgYHtyfQptaXNzaW5nX2RhdGEgPC0gcGVwICU+JQogIGV4cHJzKCkgJT4lCiAgZGF0YS5mcmFtZSgpCgpuYW5pYXI6OmdnX21pc3NfdXBzZXQobWlzc2luZ19kYXRhLAogICAgICAgICAgICAgICAgICAgICAgc2V0cyA9IHBhc3RlMChjb2xuYW1lcyhwZXApLCAnX05BJyksCiAgICAgICAgICAgICAgICAgICAgICBrZWVwLm9yZGVyID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgIG5zZXRzID0gMTApCmBgYAoKSSBEb24ndCB0aGluayB3ZSBuZWVkIHRvIGRvIGFueSBub3JtYWxpc2luZyB0byBwZXB0aWRlIGludGVuc2l0aWVzIGJlY2F1c2Ugd2UgZGlkbid0IGluamVjdCB0aGUgc2FtZSBhbW91bnQgb2YgcGVwdGlkZSwgaXQncyBhIHB1bGxkb3duLgoKTGV0J3MgZG8gc29tZSBzdW1tYXJpc2luZyB0byBwcm90ZWluLWxldmVsIGFidW5kYW5jZXMKCipSZWxheGluZyBtYXggTkEgdG8gMi8zIGFuZCByZWR1Y2luZyBtaW4gcGVwdGlkZXMgcGVyIHByb3RlaW4gdG8gMiAoZnJvbSAzKSBkb3VibGVzIHRoZSByZXRhaW5lZCBwZXB0aWRlcyB0byBcfjIwMDAuKgoKYGBge3J9CgpwZXBfcmVzdHJpY3RlZCA8LSBwZXAgJT4lCiAgIyBNYXhpbXVtIDIvMyBtaXNzaW5nIHZhbHVlcwogIE1TbmJhc2U6OmZpbHRlck5BKHBOQSA9IDIvMykgJT4lICMgV2l0aCAxOCBzYW1wbGUsIGFsbG93aW5nIG1pc3NpbmcgaW4gMTIgc2VlbXMgcmVhc29uYWJsZSB0byBtZS4KCiAgIyBBdCBsZWFzdCB0d28gcGVwdGlkZXMgcGVyIHByb3RlaW4KICBjYW1wcm90Ujo6cmVzdHJpY3RfZmVhdHVyZXNfcGVyX3Byb3RlaW4obWluX2ZlYXR1cmVzID0gMiwgcGxvdCA9IEZBTFNFKSAlPiUKCiAgIyBSZXBlYXQgdGhlIGZpbHRlcmluZyBzaW5jZSByZXN0cmljdF9mZWF0dXJlc19wZXJfcHJvdGVpbiB3aWxsIHJlcGxhY2Ugc29tZSB2YWx1ZXMgd2l0aCBOQQogIE1TbmJhc2U6OmZpbHRlck5BKHBOQSA9IDIvMykgJT4lCgogIGNhbXByb3RSOjpyZXN0cmljdF9mZWF0dXJlc19wZXJfcHJvdGVpbihtaW5fZmVhdHVyZXMgPSAyLCBwbG90ID0gRkFMU0UpCmBgYAoKYGBge3J9CnAgPC0gTVNuYmFzZTo6cGxvdE5BKHBlcF9yZXN0cmljdGVkLCBwTkEgPSAwKSArCiAgY2FtcHJvdFI6OnRoZW1lX2NhbXByb3QoYm9yZGVyID0gRkFMU0UsIGJhc2VfZmFtaWx5ID0gJ3NhbnMnLCBiYXNlX3NpemUgPSAxNSkgKwogIGxhYnMoeCA9ICdQZXB0aWRlIGluZGV4JykKYGBgCgoqV2FybmluZyBtZXNzYWdlIHJlIG1pc3NpbmcgdmFsdWVzIGNhbiBiZSBzYWZlbHkgaWdub3JlZCBoZXJlLiBgTVNuYmFzZTo6Y29tYmluZUZlYXR1cmVzYCBkb2Vzbid0IGRvIGFueSBzYW5pdHkgY2hlY2tpbmcgdGhhdCBOQSB2YWx1ZXMgYXJlIGJlaW5nIGFwcHJvcHJpYXRlbHkgaGFuZGxlZCBhbmQgZ2l2ZXMgdGhpcyB3YXJuaW5nIHNvIHRoZSBvbnVzIGlzIG9uIHRoZSB1c2VyIHRvIGtub3cgd2hhdCB0aGV5IGFyZSBkb2luZy4gYG1ldGhvZD0ncm9idXN0J2AgY2FuIGhhbmRsZSBOQSB2YWx1ZXMgYXBwcm9wcmlhdGVseS4gQW55IHByb3RlaW5zIHdpdGggTkEgdmFsdWUgZm9sbG93aW5nIHRoaXMgd2lsbCBiZSBiZWNhdXNlIHRoZSBtb2RlbCBjb3VsZCBub3QgZXN0aW1hdGUgcHJvdGVpbiBhYnVuZGFuY2UgZnJvbSB0aGUgcGVwdGlkZSBhYnVuZGFuY2UuIEZvciBleGFtcGxlLCBpbiB0aGUgZm9sbG93aW5nIHRhYmxlLCB3aGVyZSBYID0gcGVwdGlkZSBxdWFudGlmaWVkLCB0aGUgcHJvdGVpbiBjYW5ub3QgYmUgcXVhbnRpZmllZCBpbiBzYW1wbGUgQSwgc2luY2UgdGhlIHBlcHRpZGVzIHF1YW50aWZpZWQgaW4gc2FtcGxlIEEgYXJlIG5vdCBxdWFudGlmaWVkIGluIGFueSBvdGhlciBzYW1wbGUuIFRoaXMgaXMgd2h5IHdlIG5lZWQgdG8gcHJlLWZpbHRlciB0aGUgcGVwdGlkZXMgdG8gb25seSByZXRhaW4gdGhlIG1vc3QgaW5mb3JtYXRpdmUgb25lcyoKCnwgICAgICB8IEEgICB8IEIgICB8IEMgICB8IEQgICB8CnwtLS0tLS18LS0tLS18LS0tLS18LS0tLS18LS0tLS18CnwgcGVwMSB8IFggICB8ICAgICB8ICAgICB8ICAgICB8CnwgcGVwMiB8ICAgICB8IFggICB8IFggICB8IFggICB8CnwgcGVwMyB8IFggICB8ICAgICB8ICAgICB8ICAgICB8CnwgcGVwNCB8ICAgICB8IFggICB8IFggICB8IFggICB8CgpgYGB7cn0KCnByb3Rfcm9idXN0IDwtIHBlcF9yZXN0cmljdGVkICU+JQogIGxvZyhiYXNlPTIpICU+JQogIE1TbmJhc2U6OmNvbWJpbmVGZWF0dXJlcygKICAgICMgZ3JvdXAgdGhlIHBlcHRpZGVzIGJ5IHRoZWlyIG1hc3RlciBwcm90ZWluIGlkCiAgICBncm91cEJ5ID0gZkRhdGEocGVwX3Jlc3RyaWN0ZWQpJE1hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMsCiAgICBtZXRob2QgPSAncm9idXN0JywKICAgIG1heGl0ID0gMTAwMCAgIyBFbnN1cmVzIGNvbnZlcmdlbmNlIGZvciBNQVNTOjpybG0KICApCgpgYGAKCmBgYHtyfQpwIDwtIE1TbmJhc2U6OnBsb3ROQShwcm90X3JvYnVzdCwgcE5BID0gMCkgKwogIGNhbXByb3RSOjp0aGVtZV9jYW1wcm90KGJvcmRlciA9IEZBTFNFLCBiYXNlX2ZhbWlseSA9ICdzYW5zJywgYmFzZV9zaXplID0gMTUpCmBgYAoKYGBge3J9Cm5hbmlhcjo6Z2dfbWlzc191cHNldChkYXRhLmZyYW1lKGV4cHJzKHByb3Rfcm9idXN0KSksCiAgICAgICAgICAgICAgICAgICAgICBzZXRzID0gcGFzdGUwKGNvbG5hbWVzKHByb3Rfcm9idXN0KSwgJ19OQScpLAogICAgICAgICAgICAgICAgICAgICAga2VlcC5vcmRlciA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBuc2V0cyA9IDEwKQpgYGAKCmBgYHtyfQpzYXZlUkRTKHByb3Rfcm9idXN0LCAnbGZxX3Byb3Rfcm9idXN0LnJkcycpCmBgYAoKU2hhbGwgd2UgZG8gc29tZSBzdGF0aXN0aWNhbCB0ZXN0aW5nPwoKTG9hZCBpbiB0aGUgUUMnZCBMRlEgZGF0YSBmcm9tIHRoZSByZHMgZnJvbSBiZWZvcmUKCmBgYHtyfQpsZnFfcHJvdGVpbiA8LSByZWFkUkRTKCdsZnFfcHJvdF9yb2J1c3QucmRzJykKYGBgCgoqV2UgbmVlZCB0byBjb252ZXJ0IHRoZSBkYXRhIHN0cnVjdHVyZSBmcm9tIE1zblNldCB0byBhIGRhdGEuZnJhbWUgdG8gbWFrZSBpdCBlYXN5IHRvIGZpbHRlciB0byBwcm90ZWlucyB3aXRoIGF0IGxlYXN0IHR3byByZXBsaWNhdGUgd2l0aCBxdWFudGlmaWNhdGlvbiB2YWx1ZXMgZm9yIGVhY2ggc2V0IG9mIHNhbXBsZXMuIEl0IHdvdWxkIGJlIHBvc3NpYmxlIHRvIGRvIHRoaXMgd2l0aCB0aGUgTXNuU2V0LCB0aG91Z2ggSSB0aGluayBpdCB3b3VsZCBwcm92ZSBwcmV0dHkgcGFpbmZ1bCoKCk5vdGUsIGNvZGUgYmVsb3cgaXMgYW4gZXhhbXBsZSBmb3IgaG93IHRvIHRlc3QgQ0wgdnMgTkMgZm9yIHRoZSA2IExOQSBzYW1wbGVzLgpJZiB5b3UgYXJlIHBlcmZvcm1pbmcgbWFueSBkaWZmZXJlbnQgY29tcGFyaXNvbnMsIGl0IG1heSBtYWtlIHNlbnNlIHRvIGNyZWF0ZSBhIGZ1bmN0aW9uKHMpIGZvciBzb21lIG9mIHRoaXMKdG8gYXZvaWQgcmVwZWF0aW5nIHRoZSBzYW1lIGNvZGUgYW5kIGhhdmluZyBsb3RzIG9mIHNpbWlsYXJseSBuYW1lZCBvYmplY3RzIGFyb3VuZC4gSSd2ZSBpbmRpY2F0ZWQgdGhlIG1vc3Qgb2J2aW91cyBwYXJ0cyB3aGVyZSBmdW5jdGlvbnMgY291bGQgYmUgd3JpdHRlbi4KIC0gCiAtIGFkZGluZyB0aGUgY291bnRzIHRvIHRoZSBsaW1tYSBvYmplY3QKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLVRlc3QgTE5BIENMIHZzIE5DTCBkYXRhLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCgpgYGB7cn0KCmxmcV9wcm90ZWluX3RpZHlfbG5hIDwtIGxmcV9wcm90ZWluICU+JQogICMgbWFrZSB0aGUgTVNuU2V0IGludG8gJ3RpZHknIGZvcm1hdCBmb3IgZnVydGhlciB0ZXN0aW5nCiAgYmlvYnJvb206OnRpZHkuTVNuU2V0KGFkZFBoZW5vPVRSVUUpICU+JSAjIGFkZFBoZW5vPVRSVUUgYWRkcyB0aGUgcGhlbm90eXBlIGNvbHVtbnMKICBzdWJzZXQoc2FtcGxlPT0iTkNMX0xOQV8xInwgc2FtcGxlPT0iTkNMX0xOQV8yIiB8IHNhbXBsZT09Ik5DTF9MTkFfMyIgfCBzYW1wbGU9PSJDTF9MTkFfMSJ8IHNhbXBsZT09IkNMX0xOQV8yIiB8IHNhbXBsZT09IkNMX0xOQV8zIikgJT4lCiAgZmlsdGVyKGlzLmZpbml0ZSh2YWx1ZSkpICU+JQogIGdyb3VwX2J5KHByb3RlaW4sIENvbmRpdGlvbikgJT4lCiAgZmlsdGVyKG4oKT49MikgJT4lIAogIGdyb3VwX2J5KHByb3RlaW4pICU+JQogIGZpbHRlcihsZW5ndGgodW5pcXVlKENvbmRpdGlvbikpPT0yKSAlPiUjIG4oKSBpcyB0aGUgbGVuZ3RoIG9mIHRoZSBncm91cAogIHVuZ3JvdXAoKQoKICAKIyBUaGlzIGNvdWxkIGJlIGEgZnVuY3Rpb24gc2luY2UgaXQgYWx3YXlzIGJlIHRoZSBzYW1lIHN0ZXBzCiMgbWFrZV9leHByX3dpZGUgPC0gZnVuY3Rpb24odGlkeV9leHByKSB7fQpmaWx0ZXJlZF9leHByc19sbmEgPC0gbGZxX3Byb3RlaW5fdGlkeV9sbmEgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbT1zYW1wbGUsIHZhbHVlc19mcm9tPXZhbHVlLCBpZF9jb2xzPXByb3RlaW4pICU+JQogIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKCdwcm90ZWluJykgJT4lCiAgYXMubWF0cml4KCkKCiMgU2luY2UgdGhlIGNvbHVtbiBuYW1lcyBmb3Igb3VyIGZpbHRlcmVkIGV4cHJzIG1hdHJpeCBpcyBpbiB0aGUgc2FtZSBmb3JtYXQgYXMgbGZxX3Byb3RlaW4KIyB3ZSBjYW4gc3RpbGwgdXNlIHRoZSBwaGVub0RhdGEgZm9yIGxmcV9wcm90ZWluIHRvIGRlZmluZSB0aGUgY29uZGl0aW9uIGFuZAojIHJlcGxpY2F0ZSB2ZWN0b3JzLCBzbyBsb25nIGFzIHdlIHJlLW9yZGVyIGxmcV9wcm90ZWluIHVzaW5nIHRoZSBleHBycyBtYXRyaXgKIyBjb2x1bW4gbmFtZXMgZmlyc3QKdHJlYXRtZW50IDwtIHBEYXRhKGxmcV9wcm90ZWluWyxjb2xuYW1lcyhmaWx0ZXJlZF9leHByc19sbmEpXSkkQ29uZGl0aW9uCnRyZWF0bWVudCA8LSBmYWN0b3IodHJlYXRtZW50LCBsZXZlbHMgPSBjKCdDTCcsICdOQ0wnKSkKCmxpbW1hX2Rlc2lnbl9sbmEgPC0gbW9kZWwubWF0cml4KGZvcm11bGEofnRyZWF0bWVudCkpCgpsaW1tYV9maXRfbG5hIDwtIGxtRml0KGZpbHRlcmVkX2V4cHJzX2xuYSwgbGltbWFfZGVzaWduX2xuYSkKbGltbWFfZml0X2xuYSA8LSBlQmF5ZXMobGltbWFfZml0X2xuYSwgdHJlbmQ9VFJVRSkKCmxpbW1hOjpwbG90U0EobGltbWFfZml0X2xuYSkKCiMgVGhlIG5leHQgdHdvIHN0ZXBzIGNvdWxkIGJlIGEgZnVuY3Rpb24gc2luY2UgdGhleSB3aWxsIGFsd2F5cyBiZSB0aGUgc2FtZQojIGdldF9taW5fcGVwdGlkZXMgPC0gZnVuY3Rpb24oZmlsdGVyZWRfd2lkZV9leHByKSB7fQpmaWx0ZXJlZF9sZnFfcHJvdGVpbl9sb25nX2xuYSA8LSBmaWx0ZXJlZF9leHByc19sbmEgJT4lCiAgZGF0YS5mcmFtZSgpICU+JQogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCdNYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zJykgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9LU1hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMsIHZhbHVlc190bz0nYWJ1bmRhbmNlJywgbmFtZXNfdG89J3NhbXBsZScpICU+JQogIGZpbHRlcihpcy5maW5pdGUoYWJ1bmRhbmNlKSkgIyBXZSBvbmx5IHdhbnQgdG8gY29uc2lkZXIgc2FtcGxlcyB3aXRoIGEgcmF0aW8gcXVhbnRpZmllZAoKbWluX3BlcF9jb3VudF9sbmEgPC0gY2FtcHJvdFI6OmNvdW50X2ZlYXR1cmVzX3Blcl9wcm90ZWluKHBlcCkgJT4lCiAgbWVyZ2UoZmlsdGVyZWRfbGZxX3Byb3RlaW5fbG9uZ19sbmEsIGJ5PWMoJ01hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMnLCAnc2FtcGxlJykpICU+JQogIAogIGdyb3VwX2J5KE1hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMpICU+JQogIHN1bW1hcmlzZShtaW5fcGVwX2NvdW50ID0gbWluKG4pKQojIyMKCgojIGFkZCB0aGUgbWluIHBlcHRpZGUgY291bnQKbGltbWFfZml0X2xuYSRjb3VudCA8LSBtaW5fcGVwX2NvdW50X2xuYSRtaW5fcGVwX2NvdW50CgplZml0X2RlcW1zX2xuYSA8LSBzdXBwcmVzc1dhcm5pbmdzKHNwZWN0cmFDb3VudGVCYXllcyhsaW1tYV9maXRfbG5hKSkKClZhcmlhbmNlQm94cGxvdChlZml0X2RlcW1zX2xuYSwgbiA9IDMwLCB4bGFiID0gIlBlcHRpZGVzIikKCmRlcW1zX3Jlc3VsdHNfbG5hIDwtIG91dHB1dFJlc3VsdChlZml0X2RlcW1zX2xuYSwgY29lZl9jb2w9MikKYGBgCgoKYGBge3J9Cmhpc3QoZGVxbXNfcmVzdWx0c19sbmEkUC5WYWx1ZSkKaGlzdChkZXFtc19yZXN1bHRzX2xuYSRzY2EuUC5WYWx1ZSkKCmBgYAoKCmBgYHtyfQoKdGFibGUoZGVxbXNfcmVzdWx0c19sbmEkc2NhLmFkai5wdmE8MC4xKQoKZGVxbXNfcmVzdWx0c19sbmEgJT4lIGZpbHRlcihsb2dGQz4wKSAlPiUgaGVhZCgpCgpkZXFtc19yZXN1bHRzX2xuYSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChzY2EuUC5WYWx1ZSksIGNvbG91ciA9IHNjYS5hZGoucHZhbCA8IDAuMSkpICsKICBnZW9tX3BvaW50KCkgKwogIHRoZW1lX2NhbXByb3QoYm9yZGVyPUZBTFNFLCBiYXNlX3NpemU9MTUpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoJ2dyZXknLCBnZXRfY2F0X3BhbGV0dGUoMilbMl0pLCBuYW1lID0gJ0NMIHZzIE5DTCBTaWcuJykgKwogIGxhYnMoeCA9ICdMTkEgQ0wvTkNMJywgeSA9ICctbG9nMTAocC12YWx1ZSknKQpgYGAKCgoKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS1UZXN0IElPTiBDTCB2cyBOQ0wgZGF0YS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKYGBge3J9CgpsZnFfcHJvdGVpbl90aWR5X2lvbiA8LSBsZnFfcHJvdGVpbiAlPiUKICAjIG1ha2UgdGhlIE1TblNldCBpbnRvICd0aWR5JyBmb3JtYXQgZm9yIGZ1cnRoZXIgdGVzdGluZwogIGJpb2Jyb29tOjp0aWR5Lk1TblNldChhZGRQaGVubz1UUlVFKSAlPiUgIyBhZGRQaGVubz1UUlVFIGFkZHMgdGhlIHBoZW5vdHlwZSBjb2x1bW5zCiAgc3Vic2V0KHNhbXBsZT09Ik5DTF9JT05fMSJ8IHNhbXBsZT09Ik5DTF9JT05fMiIgfCBzYW1wbGU9PSJOQ0xfSU9OXzMiIHwgc2FtcGxlPT0iQ0xfSU9OXzEifCBzYW1wbGU9PSJDTF9JT05fMiIgfCBzYW1wbGU9PSJDTF9JT05fMyIpICU+JQogICMgVFM6IHdvdWxkIGJlIGJlc3QgdG8gaGF2ZSBhIGNvbHVtbiBpbiB0aGUgcERhdGEgd2hpY2ggZGVzY3JpYmVzIHRoZSBWRUgvTE5BL0lPTiB2YXJpYWJsZSwgc28geW91IGNhbiBqdXN0IGZpbHRlciB1c2luZyB0aGF0IGNvbHVtbgogIHN1YnNldChzYW1wbGUgJWluJSBjKCJOQ0xfSU9OXzEiLCAiTkNMX0lPTl8yIiwgIk5DTF9JT05fMyIsICJDTF9JT05fMSIsICJDTF9JT05fMiIsICJDTF9JT05fMyIpKSAlPiUKICBmaWx0ZXIoaXMuZmluaXRlKHZhbHVlKSkgJT4lCiAgZ3JvdXBfYnkocHJvdGVpbiwgQ29uZGl0aW9uKSAlPiUKICBmaWx0ZXIobigpPj0yKSAlPiUgCiAgZ3JvdXBfYnkocHJvdGVpbikgJT4lCiAgZmlsdGVyKGxlbmd0aCh1bmlxdWUoQ29uZGl0aW9uKSk9PTIpICU+JSMgbigpIGlzIHRoZSBsZW5ndGggb2YgdGhlIGdyb3VwCiAgdW5ncm91cCgpCgogIAojIFRoaXMgY291bGQgYmUgYSBmdW5jdGlvbiBzaW5jZSBpdCBhbHdheXMgYmUgdGhlIHNhbWUgc3RlcHMKIyBtYWtlX2V4cHJfd2lkZSA8LSBmdW5jdGlvbih0aWR5X2V4cHIpIHt9CmZpbHRlcmVkX2V4cHJzX2lvbiA8LSBsZnFfcHJvdGVpbl90aWR5X2lvbiAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tPXNhbXBsZSwgdmFsdWVzX2Zyb209dmFsdWUsIGlkX2NvbHM9cHJvdGVpbikgJT4lCiAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoJ3Byb3RlaW4nKSAlPiUKICBhcy5tYXRyaXgoKQoKIyBTaW5jZSB0aGUgY29sdW1uIG5hbWVzIGZvciBvdXIgZmlsdGVyZWQgZXhwcnMgbWF0cml4IGlzIGluIHRoZSBzYW1lIGZvcm1hdCBhcyBsZnFfcHJvdGVpbgojIHdlIGNhbiBzdGlsbCB1c2UgdGhlIHBoZW5vRGF0YSBmb3IgbGZxX3Byb3RlaW4gdG8gZGVmaW5lIHRoZSBjb25kaXRpb24gYW5kCiMgcmVwbGljYXRlIHZlY3RvcnMsIHNvIGxvbmcgYXMgd2UgcmUtb3JkZXIgbGZxX3Byb3RlaW4gdXNpbmcgdGhlIGV4cHJzIG1hdHJpeAojIGNvbHVtbiBuYW1lcyBmaXJzdAp0cmVhdG1lbnQgPC0gcERhdGEobGZxX3Byb3RlaW5bLGNvbG5hbWVzKGZpbHRlcmVkX2V4cHJzX2lvbildKSRDb25kaXRpb24KdHJlYXRtZW50IDwtIGZhY3Rvcih0cmVhdG1lbnQsIGxldmVscyA9IGMoJ0NMJywgJ05DTCcpKQoKbGltbWFfZGVzaWduX2lvbiA8LSBtb2RlbC5tYXRyaXgoZm9ybXVsYSh+dHJlYXRtZW50KSkKCmxpbW1hX2ZpdF9pb24gPC0gbG1GaXQoZmlsdGVyZWRfZXhwcnNfaW9uLCBsaW1tYV9kZXNpZ25faW9uKQpsaW1tYV9maXRfaW9uIDwtIGVCYXllcyhsaW1tYV9maXRfaW9uLCB0cmVuZD1UUlVFKQoKbGltbWE6OnBsb3RTQShsaW1tYV9maXRfaW9uKQoKIyBUaGUgbmV4dCB0d28gc3RlcHMgY291bGQgYmUgYSBmdW5jdGlvbiBzaW5jZSB0aGV5IHdpbGwgYWx3YXlzIGJlIHRoZSBzYW1lCiMgZ2V0X21pbl9wZXB0aWRlcyA8LSBmdW5jdGlvbihmaWx0ZXJlZF93aWRlX2V4cHIpIHt9CmZpbHRlcmVkX2xmcV9wcm90ZWluX2xvbmdfaW9uIDwtIGZpbHRlcmVkX2V4cHJzX2lvbiAlPiUKICBkYXRhLmZyYW1lKCkgJT4lCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oJ01hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMnKSAlPiUKICBwaXZvdF9sb25nZXIoY29scz0tTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucywgdmFsdWVzX3RvPSdhYnVuZGFuY2UnLCBuYW1lc190bz0nc2FtcGxlJykgJT4lCiAgZmlsdGVyKGlzLmZpbml0ZShhYnVuZGFuY2UpKSAjIFdlIG9ubHkgd2FudCB0byBjb25zaWRlciBzYW1wbGVzIHdpdGggYSByYXRpbyBxdWFudGlmaWVkCgptaW5fcGVwX2NvdW50X2lvbiA8LSBjYW1wcm90Ujo6Y291bnRfZmVhdHVyZXNfcGVyX3Byb3RlaW4ocGVwKSAlPiUKICBtZXJnZShmaWx0ZXJlZF9sZnFfcHJvdGVpbl9sb25nX2lvbiwgYnk9YygnTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucycsICdzYW1wbGUnKSkgJT4lCiAgCiAgZ3JvdXBfYnkoTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucykgJT4lCiAgc3VtbWFyaXNlKG1pbl9wZXBfY291bnQgPSBtaW4obikpCiMjIwoKCiMgYWRkIHRoZSBtaW4gcGVwdGlkZSBjb3VudApsaW1tYV9maXRfaW9uJGNvdW50IDwtIG1pbl9wZXBfY291bnRfaW9uJG1pbl9wZXBfY291bnQKCmVmaXRfZGVxbXNfaW9uIDwtIHN1cHByZXNzV2FybmluZ3Moc3BlY3RyYUNvdW50ZUJheWVzKGxpbW1hX2ZpdF9pb24pKQoKVmFyaWFuY2VCb3hwbG90KGVmaXRfZGVxbXNfaW9uLCBuID0gMzAsIHhsYWIgPSAiUGVwdGlkZXMiKQoKZGVxbXNfcmVzdWx0c19pb24gPC0gb3V0cHV0UmVzdWx0KGVmaXRfZGVxbXNfaW9uLCBjb2VmX2NvbD0yKQpgYGAKCgpgYGB7cn0KaGlzdChkZXFtc19yZXN1bHRzX2lvbiRQLlZhbHVlKQpoaXN0KGRlcW1zX3Jlc3VsdHNfaW9uJHNjYS5QLlZhbHVlKQoKYGBgCgoKCmBgYHtyfQoKdGFibGUoZGVxbXNfcmVzdWx0c19pb24kc2NhLmFkai5wdmE8MC4xKQoKZGVxbXNfcmVzdWx0c19pb24gJT4lIGZpbHRlcihsb2dGQz4wKSAlPiUgaGVhZCgpCgpkZXFtc19yZXN1bHRzX2lvbiAlPiUKICBnZ3Bsb3QoYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChzY2EuUC5WYWx1ZSksIGNvbG91ciA9IHNjYS5hZGoucHZhbCA8IDAuMSkpICsKICBnZW9tX3BvaW50KCkgKwogIHRoZW1lX2NhbXByb3QoYm9yZGVyPUZBTFNFLCBiYXNlX3NpemU9MTUpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoJ2dyZXknLCBnZXRfY2F0X3BhbGV0dGUoMilbMl0pLCBuYW1lID0gJ0NMIHZzIE5DTCBTaWcuJykgKwogIGxhYnMoeCA9ICdJT04gQ0wvTkNMJywgeSA9ICctbG9nMTAocC12YWx1ZSknKQpgYGAKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS1UZXN0IFZFSCBDTCB2cyBOQ0wgZGF0YS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKYGBge3J9CgpsZnFfcHJvdGVpbl90aWR5X3ZlaCA8LSBsZnFfcHJvdGVpbiAlPiUKICAjIG1ha2UgdGhlIE1TblNldCBpbnRvICd0aWR5JyBmb3JtYXQgZm9yIGZ1cnRoZXIgdGVzdGluZwogIGJpb2Jyb29tOjp0aWR5Lk1TblNldChhZGRQaGVubz1UUlVFKSAlPiUgIyBhZGRQaGVubz1UUlVFIGFkZHMgdGhlIHBoZW5vdHlwZSBjb2x1bW5zCiAgc3Vic2V0KHNhbXBsZT09Ik5DTF9WRUhfMSJ8IHNhbXBsZT09Ik5DTF9WRUhfMiIgfCBzYW1wbGU9PSJOQ0xfVkVIXzMiIHwgc2FtcGxlPT0iQ0xfVkVIXzEifCBzYW1wbGU9PSJDTF9WRUhfMiIgfCBzYW1wbGU9PSJDTF9WRUhfMyIpICU+JQogIGZpbHRlcihpcy5maW5pdGUodmFsdWUpKSAlPiUKICBncm91cF9ieShwcm90ZWluLCBDb25kaXRpb24pICU+JQogIGZpbHRlcihuKCk+PTIpICU+JSAKICBncm91cF9ieShwcm90ZWluKSAlPiUKICBmaWx0ZXIobGVuZ3RoKHVuaXF1ZShDb25kaXRpb24pKT09MikgJT4lIyBuKCkgaXMgdGhlIGxlbmd0aCBvZiB0aGUgZ3JvdXAKICB1bmdyb3VwKCkKCiAgCiMgVGhpcyBjb3VsZCBiZSBhIGZ1bmN0aW9uIHNpbmNlIGl0IGFsd2F5cyBiZSB0aGUgc2FtZSBzdGVwcwojIG1ha2VfZXhwcl93aWRlIDwtIGZ1bmN0aW9uKHRpZHlfZXhwcikge30KZmlsdGVyZWRfZXhwcnNfdmVoIDwtIGxmcV9wcm90ZWluX3RpZHlfdmVoICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb209c2FtcGxlLCB2YWx1ZXNfZnJvbT12YWx1ZSwgaWRfY29scz1wcm90ZWluKSAlPiUKICB0aWJibGU6OmNvbHVtbl90b19yb3duYW1lcygncHJvdGVpbicpICU+JQogIGFzLm1hdHJpeCgpCgojIFNpbmNlIHRoZSBjb2x1bW4gbmFtZXMgZm9yIG91ciBmaWx0ZXJlZCBleHBycyBtYXRyaXggaXMgaW4gdGhlIHNhbWUgZm9ybWF0IGFzIGxmcV9wcm90ZWluCiMgd2UgY2FuIHN0aWxsIHVzZSB0aGUgcGhlbm9EYXRhIGZvciBsZnFfcHJvdGVpbiB0byBkZWZpbmUgdGhlIGNvbmRpdGlvbiBhbmQKIyByZXBsaWNhdGUgdmVjdG9ycywgc28gbG9uZyBhcyB3ZSByZS1vcmRlciBsZnFfcHJvdGVpbiB1c2luZyB0aGUgZXhwcnMgbWF0cml4CiMgY29sdW1uIG5hbWVzIGZpcnN0CnRyZWF0bWVudCA8LSBwRGF0YShsZnFfcHJvdGVpblssY29sbmFtZXMoZmlsdGVyZWRfZXhwcnNfdmVoKV0pJENvbmRpdGlvbgp0cmVhdG1lbnQgPC0gZmFjdG9yKHRyZWF0bWVudCwgbGV2ZWxzID0gYygnQ0wnLCAnTkNMJykpCgpsaW1tYV9kZXNpZ25fdmVoIDwtIG1vZGVsLm1hdHJpeChmb3JtdWxhKH50cmVhdG1lbnQpKQoKbGltbWFfZml0X3ZlaCA8LSBsbUZpdChmaWx0ZXJlZF9leHByc192ZWgsIGxpbW1hX2Rlc2lnbl92ZWgpCmxpbW1hX2ZpdF92ZWggPC0gZUJheWVzKGxpbW1hX2ZpdF92ZWgsIHRyZW5kPVRSVUUpCgpsaW1tYTo6cGxvdFNBKGxpbW1hX2ZpdF92ZWgpCgojIFRoZSBuZXh0IHR3byBzdGVwcyBjb3VsZCBiZSBhIGZ1bmN0aW9uIHNpbmNlIHRoZXkgd2lsbCBhbHdheXMgYmUgdGhlIHNhbWUKIyBnZXRfbWluX3BlcHRpZGVzIDwtIGZ1bmN0aW9uKGZpbHRlcmVkX3dpZGVfZXhwcikge30KZmlsdGVyZWRfbGZxX3Byb3RlaW5fbG9uZ192ZWggPC0gZmlsdGVyZWRfZXhwcnNfdmVoICU+JQogIGRhdGEuZnJhbWUoKSAlPiUKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbignTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucycpICU+JQogIHBpdm90X2xvbmdlcihjb2xzPS1NYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zLCB2YWx1ZXNfdG89J2FidW5kYW5jZScsIG5hbWVzX3RvPSdzYW1wbGUnKSAlPiUKICBmaWx0ZXIoaXMuZmluaXRlKGFidW5kYW5jZSkpICMgV2Ugb25seSB3YW50IHRvIGNvbnNpZGVyIHNhbXBsZXMgd2l0aCBhIHJhdGlvIHF1YW50aWZpZWQKCm1pbl9wZXBfY291bnRfdmVoIDwtIGNhbXByb3RSOjpjb3VudF9mZWF0dXJlc19wZXJfcHJvdGVpbihwZXApICU+JQogIG1lcmdlKGZpbHRlcmVkX2xmcV9wcm90ZWluX2xvbmdfdmVoLCBieT1jKCdNYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zJywgJ3NhbXBsZScpKSAlPiUKICAKICBncm91cF9ieShNYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zKSAlPiUKICBzdW1tYXJpc2UobWluX3BlcF9jb3VudCA9IG1pbihuKSkKIyMjCgoKIyBhZGQgdGhlIG1pbiBwZXB0aWRlIGNvdW50CmxpbW1hX2ZpdF92ZWgkY291bnQgPC0gbWluX3BlcF9jb3VudF92ZWgkbWluX3BlcF9jb3VudAoKZWZpdF9kZXFtc192ZWggPC0gc3VwcHJlc3NXYXJuaW5ncyhzcGVjdHJhQ291bnRlQmF5ZXMobGltbWFfZml0X3ZlaCkpCgpWYXJpYW5jZUJveHBsb3QoZWZpdF9kZXFtc192ZWgsIG4gPSAzMCwgeGxhYiA9ICJQZXB0aWRlcyIpCgpkZXFtc19yZXN1bHRzX3ZlaCA8LSBvdXRwdXRSZXN1bHQoZWZpdF9kZXFtc192ZWgsIGNvZWZfY29sPTIpCmBgYAoKCmBgYHtyfQpoaXN0KGRlcW1zX3Jlc3VsdHNfdmVoJFAuVmFsdWUpCmhpc3QoZGVxbXNfcmVzdWx0c192ZWgkc2NhLlAuVmFsdWUpCgpgYGAKCgpgYGB7cn0KCnRhYmxlKGRlcW1zX3Jlc3VsdHNfdmVoJHNjYS5hZGoucHZhPDAuMSkKCmRlcW1zX3Jlc3VsdHNfdmVoICU+JSBmaWx0ZXIobG9nRkM+MCkgJT4lIGhlYWQoKQoKZGVxbXNfcmVzdWx0c192ZWggJT4lCiAgZ2dwbG90KGFlcyh4ID0gbG9nRkMsIHkgPSAtbG9nMTAoc2NhLlAuVmFsdWUpLCBjb2xvdXIgPSBzY2EuYWRqLnB2YWwgPCAwLjEpKSArCiAgZ2VvbV9wb2ludCgpICsKICB0aGVtZV9jYW1wcm90KGJvcmRlcj1GQUxTRSwgYmFzZV9zaXplPTE1KSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCdncmV5JywgZ2V0X2NhdF9wYWxldHRlKDIpWzJdKSwgbmFtZSA9ICdDTCB2cyBOQ0wgU2lnLicpICsKICBsYWJzKHggPSAnVkVIX0NML05DTCcsIHkgPSAnLWxvZzEwKHAtdmFsdWUpJykKYGBgCgoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLVRlc3QgSU9OIENMIHZzIExOQSBDTCBkYXRhLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCkxldCdzIHZpc3VhbGlzZSB0aGUgYWJ1bmRhbmNlIGRpc3RyaWJ1dGlvbnMgb2YgdGhlIHNhbXBsZXMgaW4gcXVlc3Rpb24uCmBgYHtyfQpwbG90X3F1YW50KGxmcV9wcm90ZWluWyxncmVwbCgnXkNMXyhJT058TE5BKV9cXGQnLCBjb2xuYW1lcyhsZnFfcHJvdGVpbikpXSwgbWV0aG9kPSdkZW5zaXR5JykgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPXJlcChnZXRfY2F0X3BhbGV0dGUoMiksIGVhY2g9MykpCnBsb3RfcXVhbnQobGZxX3Byb3RlaW5bLGdyZXBsKCdeQ0xfKElPTnxMTkEpX1xcZCcsIGNvbG5hbWVzKGxmcV9wcm90ZWluKSldLCBtZXRob2Q9J2JveCcpCmBgYAoKCgpgYGB7cn0KbGZxX3Byb3RlaW5fdGlkeV9JT05fTE5BIDwtIGxmcV9wcm90ZWluICU+JQogICMgbWFrZSB0aGUgTVNuU2V0IGludG8gJ3RpZHknIGZvcm1hdCBmb3IgZnVydGhlciB0ZXN0aW5nCiAgYmlvYnJvb206OnRpZHkuTVNuU2V0KGFkZFBoZW5vPVRSVUUpICU+JSAjIGFkZFBoZW5vPVRSVUUgYWRkcyB0aGUgcGhlbm90eXBlIGNvbHVtbnMKICBmaWx0ZXIoZ3JlcGwoJ15DTF8oSU9OfExOQSlfXFxkJywgc2FtcGxlKSkgJT4lCiAgc2VwYXJhdGUoc2FtcGxlLCBpbnRvPWMoTkEsICdUcmVhdG1lbnQnLCBOQSksIHJlbW92ZSA9IEZBTFNFKSAlPiUKICBmaWx0ZXIoaXMuZmluaXRlKHZhbHVlKSkgJT4lCiAgZ3JvdXBfYnkocHJvdGVpbikgJT4lCiAgZmlsdGVyKG4oKT49MikgJT4lIAogIGdyb3VwX2J5KHByb3RlaW4pICU+JQogIGZpbHRlcihsZW5ndGgodW5pcXVlKFRyZWF0bWVudCkpPT0yKSAlPiUjIG4oKSBpcyB0aGUgbGVuZ3RoIG9mIHRoZSBncm91cAogIHVuZ3JvdXAoKQoKICAKIyBUaGlzIGNvdWxkIGJlIGEgZnVuY3Rpb24gc2luY2UgaXQgYWx3YXlzIGJlIHRoZSBzYW1lIHN0ZXBzCiMgbWFrZV9leHByX3dpZGUgPC0gZnVuY3Rpb24odGlkeV9leHByKSB7fQpmaWx0ZXJlZF9leHByc19pb25fbG5hX2NsIDwtIGxmcV9wcm90ZWluX3RpZHlfSU9OX0xOQSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tPXNhbXBsZSwgdmFsdWVzX2Zyb209dmFsdWUsIGlkX2NvbHM9cHJvdGVpbikgJT4lCiAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoJ3Byb3RlaW4nKSAlPiUKICBhcy5tYXRyaXgoKQoKIyBTaW5jZSB0aGUgY29sdW1uIG5hbWVzIGZvciBvdXIgZmlsdGVyZWQgZXhwcnMgbWF0cml4IGlzIGluIHRoZSBzYW1lIGZvcm1hdCBhcyBsZnFfcHJvdGVpbgojIHdlIGNhbiBzdGlsbCB1c2UgdGhlIHBoZW5vRGF0YSBmb3IgbGZxX3Byb3RlaW4gdG8gZGVmaW5lIHRoZSBjb25kaXRpb24gYW5kCiMgcmVwbGljYXRlIHZlY3RvcnMsIHNvIGxvbmcgYXMgd2UgcmUtb3JkZXIgbGZxX3Byb3RlaW4gdXNpbmcgdGhlIGV4cHJzIG1hdHJpeAojIGNvbHVtbiBuYW1lcyBmaXJzdAoKbGlicmFyeSh0aWJibGUpCgpwaGVub19kYXRhMjwtIHJvd25hbWVzX3RvX2NvbHVtbihwRGF0YShsZnFfcHJvdGVpblssY29sbmFtZXMoZmlsdGVyZWRfZXhwcnNfaW9uX2xuYV9jbCldKSwgdmFyID0gInNhbXBsZSIpCnBoZW5vX2RhdGEyIDwtIHBoZW5vX2RhdGEyICU+JSBzZXBhcmF0ZShzYW1wbGUsIGludG89YyhOQSwgJ1RyZWF0bWVudCcsIE5BKSwgcmVtb3ZlID0gRkFMU0UpIAoKdHJlYXRtZW50MiA8LSBwaGVub19kYXRhMiRUcmVhdG1lbnQKdHJlYXRtZW50MiA8LSBmYWN0b3IodHJlYXRtZW50MiwgbGV2ZWxzID0gYygnSU9OJywgJ0xOQScpKQpyZXBsaWNhdGUgPC0gcGhlbm9fZGF0YTIkUmVwbGljYXRlCgpsaW1tYV9kZXNpZ25faW9uX2xuYV9jbCA8LSBtb2RlbC5tYXRyaXgoZm9ybXVsYSh+cmVwbGljYXRlK3RyZWF0bWVudDIpKQoKbGltbWFfZml0X2lvbl9sbmFfY2wgPC0gbG1GaXQoZmlsdGVyZWRfZXhwcnNfaW9uX2xuYV9jbCwgbGltbWFfZGVzaWduX2lvbl9sbmFfY2wpCmxpbW1hX2ZpdF9pb25fbG5hX2NsIDwtIGVCYXllcyhsaW1tYV9maXRfaW9uX2xuYV9jbCwgdHJlbmQ9VFJVRSkKCmxpbW1hOjpwbG90U0EobGltbWFfZml0X2lvbl9sbmFfY2wpCgojIFRoZSBuZXh0IHR3byBzdGVwcyBjb3VsZCBiZSBhIGZ1bmN0aW9uIHNpbmNlIHRoZXkgd2lsbCBhbHdheXMgYmUgdGhlIHNhbWUKIyBnZXRfbWluX3BlcHRpZGVzIDwtIGZ1bmN0aW9uKGZpbHRlcmVkX3dpZGVfZXhwcikge30KZmlsdGVyZWRfbGZxX3Byb3RlaW5fbG9uZ19pb25fbG5hX2NsIDwtIGZpbHRlcmVkX2V4cHJzX2lvbl9sbmFfY2wgJT4lCiAgZGF0YS5mcmFtZSgpICU+JQogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCdNYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zJykgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9LU1hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMsIHZhbHVlc190bz0nYWJ1bmRhbmNlJywgbmFtZXNfdG89J3NhbXBsZScpICU+JQogIGZpbHRlcihpcy5maW5pdGUoYWJ1bmRhbmNlKSkgIyBXZSBvbmx5IHdhbnQgdG8gY29uc2lkZXIgc2FtcGxlcyB3aXRoIGEgcmF0aW8gcXVhbnRpZmllZAoKbWluX3BlcF9jb3VudF9pb25fbG5hX2NsIDwtIGNhbXByb3RSOjpjb3VudF9mZWF0dXJlc19wZXJfcHJvdGVpbihwZXApICU+JQogIG1lcmdlKGZpbHRlcmVkX2xmcV9wcm90ZWluX2xvbmdfaW9uX2xuYV9jbCwgYnk9YygnTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucycsICdzYW1wbGUnKSkgJT4lCiAgCiAgZ3JvdXBfYnkoTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucykgJT4lCiAgc3VtbWFyaXNlKG1pbl9wZXBfY291bnQgPSBtaW4obikpCiMjIwoKCiMgYWRkIHRoZSBtaW4gcGVwdGlkZSBjb3VudApsaW1tYV9maXRfaW9uX2xuYV9jbCRjb3VudCA8LSBtaW5fcGVwX2NvdW50X2lvbl9sbmFfY2wkbWluX3BlcF9jb3VudAoKZWZpdF9kZXFtc19pb25fbG5hX2NsIDwtIHN1cHByZXNzV2FybmluZ3Moc3BlY3RyYUNvdW50ZUJheWVzKGxpbW1hX2ZpdF9pb25fbG5hX2NsKSkKClZhcmlhbmNlQm94cGxvdChlZml0X2RlcW1zX2lvbl9sbmFfY2wsIG4gPSAzMCwgeGxhYiA9ICJQZXB0aWRlcyIpCgpoZWFkKGNvZWZmaWNpZW50cyhlZml0X2RlcW1zX2lvbl9sbmFfY2wpKQoKZGVxbXNfcmVzdWx0c19pb25fbG5hX2NsIDwtIG91dHB1dFJlc3VsdChlZml0X2RlcW1zX2lvbl9sbmFfY2wsIGNvZWZfY29sPTMpCmBgYAoKCmBgYHtyfQpoaXN0KGRlcW1zX3Jlc3VsdHNfaW9uX2xuYV9jbCRQLlZhbHVlKQpoaXN0KGRlcW1zX3Jlc3VsdHNfaW9uX2xuYV9jbCRzY2EuUC5WYWx1ZSkKCmBgYAoKCmBgYHtyfQoKdGFibGUoZGVxbXNfcmVzdWx0c19pb25fbG5hX2NsJHNjYS5hZGoucHZhPDAuMSkKCmRlcW1zX3Jlc3VsdHNfaW9uX2xuYV9jbCAlPiUgZmlsdGVyKGxvZ0ZDPjApICU+JSBoZWFkKCkKCmRlcW1zX3Jlc3VsdHNfdmVoICU+JQogIGdncGxvdChhZXMoeCA9IGxvZ0ZDLCB5ID0gLWxvZzEwKHNjYS5QLlZhbHVlKSwgY29sb3VyID0gc2NhLmFkai5wdmFsIDwgMC4xKSkgKwogIGdlb21fcG9pbnQoKSArCiAgdGhlbWVfY2FtcHJvdChib3JkZXI9RkFMU0UsIGJhc2Vfc2l6ZT0xNSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygnZ3JleScsIGdldF9jYXRfcGFsZXR0ZSgyKVsyXSksIG5hbWUgPSAnSU9OIHZzIExOQSBTaWcuJykgKwogIGxhYnMoeCA9ICdJb25DTCB2cyBMTkFDTCAoTG9nMiknLCB5ID0gJy1sb2cxMChwLXZhbHVlKScpCmBgYAoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCgotLS0tLS0tLS0tLS0tLS0tLS0tLS1MTkEgdiBWRUgtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKCmBgYHtyfQoKbGZxX3Byb3RlaW5fdGlkeV9MTkFfVkVIIDwtIGxmcV9wcm90ZWluICU+JQogICMgbWFrZSB0aGUgTVNuU2V0IGludG8gJ3RpZHknIGZvcm1hdCBmb3IgZnVydGhlciB0ZXN0aW5nCiAgYmlvYnJvb206OnRpZHkuTVNuU2V0KGFkZFBoZW5vPVRSVUUpICU+JSAjIGFkZFBoZW5vPVRSVUUgYWRkcyB0aGUgcGhlbm90eXBlIGNvbHVtbnMKICBmaWx0ZXIoZ3JlcGwoJ15DTF8oTE5BfFZFSClfXFxkJywgc2FtcGxlKSkgJT4lCiAgc2VwYXJhdGUoc2FtcGxlLCBpbnRvPWMoTkEsICdUcmVhdG1lbnQnLCBOQSksIHJlbW92ZSA9IEZBTFNFKSAlPiUKICBmaWx0ZXIoaXMuZmluaXRlKHZhbHVlKSkgJT4lCiAgZ3JvdXBfYnkocHJvdGVpbikgJT4lCiAgZmlsdGVyKG4oKT49MikgJT4lIAogIGdyb3VwX2J5KHByb3RlaW4pICU+JQogIGZpbHRlcihsZW5ndGgodW5pcXVlKFRyZWF0bWVudCkpPT0yKSAlPiUjIG4oKSBpcyB0aGUgbGVuZ3RoIG9mIHRoZSBncm91cAogIHVuZ3JvdXAoKQoKICAKIyBUaGlzIGNvdWxkIGJlIGEgZnVuY3Rpb24gc2luY2UgaXQgYWx3YXlzIGJlIHRoZSBzYW1lIHN0ZXBzCiMgbWFrZV9leHByX3dpZGUgPC0gZnVuY3Rpb24odGlkeV9leHByKSB7fQpmaWx0ZXJlZF9leHByc19sbmFfdmVoX2NsIDwtIGxmcV9wcm90ZWluX3RpZHlfTE5BX1ZFSCAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tPXNhbXBsZSwgdmFsdWVzX2Zyb209dmFsdWUsIGlkX2NvbHM9cHJvdGVpbikgJT4lCiAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoJ3Byb3RlaW4nKSAlPiUKICBhcy5tYXRyaXgoKQoKIyBTaW5jZSB0aGUgY29sdW1uIG5hbWVzIGZvciBvdXIgZmlsdGVyZWQgZXhwcnMgbWF0cml4IGlzIGluIHRoZSBzYW1lIGZvcm1hdCBhcyBsZnFfcHJvdGVpbgojIHdlIGNhbiBzdGlsbCB1c2UgdGhlIHBoZW5vRGF0YSBmb3IgbGZxX3Byb3RlaW4gdG8gZGVmaW5lIHRoZSBjb25kaXRpb24gYW5kCiMgcmVwbGljYXRlIHZlY3RvcnMsIHNvIGxvbmcgYXMgd2UgcmUtb3JkZXIgbGZxX3Byb3RlaW4gdXNpbmcgdGhlIGV4cHJzIG1hdHJpeAojIGNvbHVtbiBuYW1lcyBmaXJzdAoKbGlicmFyeSh0aWJibGUpCgpwaGVub19kYXRhMjwtIHJvd25hbWVzX3RvX2NvbHVtbihwRGF0YShsZnFfcHJvdGVpblssY29sbmFtZXMoZmlsdGVyZWRfZXhwcnNfbG5hX3ZlaF9jbCldKSwgdmFyID0gInNhbXBsZSIpCnBoZW5vX2RhdGEyIDwtIHBoZW5vX2RhdGEyICU+JSBzZXBhcmF0ZShzYW1wbGUsIGludG89YyhOQSwgJ1RyZWF0bWVudCcsIE5BKSwgcmVtb3ZlID0gRkFMU0UpIAoKdHJlYXRtZW50MiA8LSBwaGVub19kYXRhMiRUcmVhdG1lbnQKdHJlYXRtZW50MiA8LSBmYWN0b3IodHJlYXRtZW50MiwgbGV2ZWxzID0gYygnTE5BJywgJ1ZFSCcpKQpyZXBsaWNhdGUgPC0gcGhlbm9fZGF0YTIkUmVwbGljYXRlCgpsaW1tYV9kZXNpZ25fbG5hX3ZlaF9jbCA8LSBtb2RlbC5tYXRyaXgoZm9ybXVsYSh+cmVwbGljYXRlK3RyZWF0bWVudDIpKQoKbGltbWFfZml0X2xuYV92ZWhfY2wgPC0gbG1GaXQoZmlsdGVyZWRfZXhwcnNfbG5hX3ZlaF9jbCwgbGltbWFfZGVzaWduX2xuYV92ZWhfY2wpCmxpbW1hX2ZpdF9sbmFfdmVoX2NsIDwtIGVCYXllcyhsaW1tYV9maXRfbG5hX3ZlaF9jbCwgdHJlbmQ9VFJVRSkKCmxpbW1hOjpwbG90U0EobGltbWFfZml0X2xuYV92ZWhfY2wpCgojIFRoZSBuZXh0IHR3byBzdGVwcyBjb3VsZCBiZSBhIGZ1bmN0aW9uIHNpbmNlIHRoZXkgd2lsbCBhbHdheXMgYmUgdGhlIHNhbWUKIyBnZXRfbWluX3BlcHRpZGVzIDwtIGZ1bmN0aW9uKGZpbHRlcmVkX3dpZGVfZXhwcikge30KZmlsdGVyZWRfbGZxX3Byb3RlaW5fbG9uZ19sbmFfdmVoX2NsIDwtIGZpbHRlcmVkX2V4cHJzX2xuYV92ZWhfY2wgJT4lCiAgZGF0YS5mcmFtZSgpICU+JQogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCdNYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zJykgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9LU1hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMsIHZhbHVlc190bz0nYWJ1bmRhbmNlJywgbmFtZXNfdG89J3NhbXBsZScpICU+JQogIGZpbHRlcihpcy5maW5pdGUoYWJ1bmRhbmNlKSkgIyBXZSBvbmx5IHdhbnQgdG8gY29uc2lkZXIgc2FtcGxlcyB3aXRoIGEgcmF0aW8gcXVhbnRpZmllZAoKbWluX3BlcF9jb3VudF9sbmFfdmVoX2NsIDwtIGNhbXByb3RSOjpjb3VudF9mZWF0dXJlc19wZXJfcHJvdGVpbihwZXApICU+JQogIG1lcmdlKGZpbHRlcmVkX2xmcV9wcm90ZWluX2xvbmdfbG5hX3ZlaF9jbCwgYnk9YygnTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucycsICdzYW1wbGUnKSkgJT4lCiAgCiAgZ3JvdXBfYnkoTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucykgJT4lCiAgc3VtbWFyaXNlKG1pbl9wZXBfY291bnQgPSBtaW4obikpCiMjIwoKCiMgYWRkIHRoZSBtaW4gcGVwdGlkZSBjb3VudApsaW1tYV9maXRfbG5hX3ZlaF9jbCRjb3VudCA8LSBtaW5fcGVwX2NvdW50X2xuYV92ZWhfY2wkbWluX3BlcF9jb3VudAoKZWZpdF9kZXFtc19sbmFfdmVoX2NsIDwtIHN1cHByZXNzV2FybmluZ3Moc3BlY3RyYUNvdW50ZUJheWVzKGxpbW1hX2ZpdF9sbmFfdmVoX2NsKSkKClZhcmlhbmNlQm94cGxvdChlZml0X2RlcW1zX2xuYV92ZWhfY2wsIG4gPSAzMCwgeGxhYiA9ICJQZXB0aWRlcyIpCgpoZWFkKGNvZWZmaWNpZW50cyhlZml0X2RlcW1zX2xuYV92ZWhfY2wpKQoKZGVxbXNfcmVzdWx0c19sbmFfdmVoX2NsIDwtIG91dHB1dFJlc3VsdChlZml0X2RlcW1zX2xuYV92ZWhfY2wsIGNvZWZfY29sPTMpCgpgYGAKCgpgYGB7cn0KaGlzdChkZXFtc19yZXN1bHRzX2xuYV92ZWhfY2wkUC5WYWx1ZSkKaGlzdChkZXFtc19yZXN1bHRzX2xuYV92ZWhfY2wkc2NhLlAuVmFsdWUpCgpgYGAKCgpgYGB7cn0KCnRhYmxlKGRlcW1zX3Jlc3VsdHNfbG5hX3ZlaF9jbCRzY2EuYWRqLnB2YWw8MC4xKQoKZGVxbXNfcmVzdWx0c19sbmFfdmVoX2NsICU+JSBmaWx0ZXIobG9nRkM+MCkgJT4lIGhlYWQoKQoKZGVxbXNfcmVzdWx0c19sbmFfdmVoX2NsICU+JQogIGdncGxvdChhZXMoeCA9IGxvZ0ZDLCB5ID0gLWxvZzEwKHNjYS5QLlZhbHVlKSwgY29sb3VyID0gc2NhLmFkai5wdmFsIDwgMC4xKSkgKwogIGdlb21fcG9pbnQoKSArCiAgdGhlbWVfY2FtcHJvdChib3JkZXI9RkFMU0UsIGJhc2Vfc2l6ZT0xNSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygnZ3JleScsIGdldF9jYXRfcGFsZXR0ZSgyKVsyXSksIG5hbWUgPSAnTE5BIHZzIFZFSCBTaWcuJykgKwogIGxhYnMoeCA9ICdMTkFDTCB2cyBWRUhDTCAoTG9nMiknLCB5ID0gJy1sb2cxMChwLXZhbHVlKScpCmBgYAoKCgotLS0tLS0tLS0tLS0tLS0tLS0tLS1JT05fQ0x2IFZFSF9DTC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgpMZXQncyB2aXN1YWxpc2UgdGhlIGFidW5kYW5jZSBkaXN0cmlidXRpb25zIG9mIHRoZSBzYW1wbGVzIGluIHF1ZXN0aW9uLgpgYGB7cn0KcGxvdF9xdWFudChsZnFfcHJvdGVpblssZ3JlcGwoJ15DTF8oSU9OfFZFSClfXFxkJywgY29sbmFtZXMobGZxX3Byb3RlaW4pKV0sIG1ldGhvZD0nZGVuc2l0eScpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1yZXAoZ2V0X2NhdF9wYWxldHRlKDIpLCBlYWNoPTMpKQpwbG90X3F1YW50KGxmcV9wcm90ZWluWyxncmVwbCgnXkNMXyhJT058VkVIKV9cXGQnLCBjb2xuYW1lcyhsZnFfcHJvdGVpbikpXSwgbWV0aG9kPSdib3gnKQpgYGAKCgoKYGBge3J9CgpsZnFfcHJvdGVpbl90aWR5X0lPTl9WRUggPC0gbGZxX3Byb3RlaW4gJT4lCiAgIyBtYWtlIHRoZSBNU25TZXQgaW50byAndGlkeScgZm9ybWF0IGZvciBmdXJ0aGVyIHRlc3RpbmcKICBiaW9icm9vbTo6dGlkeS5NU25TZXQoYWRkUGhlbm89VFJVRSkgJT4lICMgYWRkUGhlbm89VFJVRSBhZGRzIHRoZSBwaGVub3R5cGUgY29sdW1ucwogIGZpbHRlcihncmVwbCgnXkNMXyhJT058VkVIKV9cXGQnLCBzYW1wbGUpKSAlPiUKICBzZXBhcmF0ZShzYW1wbGUsIGludG89YyhOQSwgJ1RyZWF0bWVudCcsIE5BKSwgcmVtb3ZlID0gRkFMU0UpICU+JQogIGZpbHRlcihpcy5maW5pdGUodmFsdWUpKSAlPiUKICBncm91cF9ieShwcm90ZWluKSAlPiUKICBmaWx0ZXIobigpPj0yKSAlPiUgCiAgZ3JvdXBfYnkocHJvdGVpbikgJT4lCiAgZmlsdGVyKGxlbmd0aCh1bmlxdWUoVHJlYXRtZW50KSk9PTIpICU+JSMgbigpIGlzIHRoZSBsZW5ndGggb2YgdGhlIGdyb3VwCiAgdW5ncm91cCgpCgogIAojIFRoaXMgY291bGQgYmUgYSBmdW5jdGlvbiBzaW5jZSBpdCBhbHdheXMgYmUgdGhlIHNhbWUgc3RlcHMKIyBtYWtlX2V4cHJfd2lkZSA8LSBmdW5jdGlvbih0aWR5X2V4cHIpIHt9CmZpbHRlcmVkX2V4cHJzX2lvbl92ZWhfY2wgPC0gbGZxX3Byb3RlaW5fdGlkeV9JT05fVkVIICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb209c2FtcGxlLCB2YWx1ZXNfZnJvbT12YWx1ZSwgaWRfY29scz1wcm90ZWluKSAlPiUKICB0aWJibGU6OmNvbHVtbl90b19yb3duYW1lcygncHJvdGVpbicpICU+JQogIGFzLm1hdHJpeCgpCgojIFNpbmNlIHRoZSBjb2x1bW4gbmFtZXMgZm9yIG91ciBmaWx0ZXJlZCBleHBycyBtYXRyaXggaXMgaW4gdGhlIHNhbWUgZm9ybWF0IGFzIGxmcV9wcm90ZWluCiMgd2UgY2FuIHN0aWxsIHVzZSB0aGUgcGhlbm9EYXRhIGZvciBsZnFfcHJvdGVpbiB0byBkZWZpbmUgdGhlIGNvbmRpdGlvbiBhbmQKIyByZXBsaWNhdGUgdmVjdG9ycywgc28gbG9uZyBhcyB3ZSByZS1vcmRlciBsZnFfcHJvdGVpbiB1c2luZyB0aGUgZXhwcnMgbWF0cml4CiMgY29sdW1uIG5hbWVzIGZpcnN0CgpsaWJyYXJ5KHRpYmJsZSkKCnBoZW5vX2RhdGEyPC0gcm93bmFtZXNfdG9fY29sdW1uKHBEYXRhKGxmcV9wcm90ZWluWyxjb2xuYW1lcyhmaWx0ZXJlZF9leHByc19pb25fdmVoX2NsKV0pLCB2YXIgPSAic2FtcGxlIikKcGhlbm9fZGF0YTIgPC0gcGhlbm9fZGF0YTIgJT4lIHNlcGFyYXRlKHNhbXBsZSwgaW50bz1jKE5BLCAnVHJlYXRtZW50JywgTkEpLCByZW1vdmUgPSBGQUxTRSkgCgp0cmVhdG1lbnQyIDwtIHBoZW5vX2RhdGEyJFRyZWF0bWVudAp0cmVhdG1lbnQyIDwtIGZhY3Rvcih0cmVhdG1lbnQyLCBsZXZlbHMgPSBjKCdWRUgnLCAnSU9OJykpCnJlcGxpY2F0ZSA8LSBwaGVub19kYXRhMiRSZXBsaWNhdGUKCmxpbW1hX2Rlc2lnbl9pb25fdmVoX2NsIDwtIG1vZGVsLm1hdHJpeChmb3JtdWxhKH5yZXBsaWNhdGUrdHJlYXRtZW50MikpCgpsaW1tYV9maXRfaW9uX3ZlaF9jbCA8LSBsbUZpdChmaWx0ZXJlZF9leHByc19pb25fdmVoX2NsLCBsaW1tYV9kZXNpZ25faW9uX3ZlaF9jbCkKbGltbWFfZml0X2lvbl92ZWhfY2wgPC0gZUJheWVzKGxpbW1hX2ZpdF9pb25fdmVoX2NsLCB0cmVuZD1UUlVFKQoKbGltbWE6OnBsb3RTQShsaW1tYV9maXRfaW9uX3ZlaF9jbCkKCiMgVGhlIG5leHQgdHdvIHN0ZXBzIGNvdWxkIGJlIGEgZnVuY3Rpb24gc2luY2UgdGhleSB3aWxsIGFsd2F5cyBiZSB0aGUgc2FtZQojIGdldF9taW5fcGVwdGlkZXMgPC0gZnVuY3Rpb24oZmlsdGVyZWRfd2lkZV9leHByKSB7fQpmaWx0ZXJlZF9sZnFfcHJvdGVpbl9sb25nX2lvbl92ZWhfY2wgPC0gZmlsdGVyZWRfZXhwcnNfaW9uX3ZlaF9jbCAlPiUKICBkYXRhLmZyYW1lKCkgJT4lCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oJ01hc3Rlci5Qcm90ZWluLkFjY2Vzc2lvbnMnKSAlPiUKICBwaXZvdF9sb25nZXIoY29scz0tTWFzdGVyLlByb3RlaW4uQWNjZXNzaW9ucywgdmFsdWVzX3RvPSdhYnVuZGFuY2UnLCBuYW1lc190bz0nc2FtcGxlJykgJT4lCiAgZmlsdGVyKGlzLmZpbml0ZShhYnVuZGFuY2UpKSAjIFdlIG9ubHkgd2FudCB0byBjb25zaWRlciBzYW1wbGVzIHdpdGggYSByYXRpbyBxdWFudGlmaWVkCgptaW5fcGVwX2NvdW50X2lvbl92ZWhfY2wgPC0gY2FtcHJvdFI6OmNvdW50X2ZlYXR1cmVzX3Blcl9wcm90ZWluKHBlcCkgJT4lCiAgbWVyZ2UoZmlsdGVyZWRfbGZxX3Byb3RlaW5fbG9uZ19pb25fdmVoX2NsLCBieT1jKCdNYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zJywgJ3NhbXBsZScpKSAlPiUKICAKICBncm91cF9ieShNYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zKSAlPiUKICBzdW1tYXJpc2UobWluX3BlcF9jb3VudCA9IG1pbihuKSkKIyMjCgoKIyBhZGQgdGhlIG1pbiBwZXB0aWRlIGNvdW50CmxpbW1hX2ZpdF9pb25fdmVoX2NsJGNvdW50IDwtIG1pbl9wZXBfY291bnRfaW9uX3ZlaF9jbCRtaW5fcGVwX2NvdW50CgplZml0X2RlcW1zX2lvbl92ZWhfY2wgPC0gc3VwcHJlc3NXYXJuaW5ncyhzcGVjdHJhQ291bnRlQmF5ZXMobGltbWFfZml0X2lvbl92ZWhfY2wpKQoKVmFyaWFuY2VCb3hwbG90KGVmaXRfZGVxbXNfaW9uX3ZlaF9jbCwgbiA9IDMwLCB4bGFiID0gIlBlcHRpZGVzIikKCmhlYWQoY29lZmZpY2llbnRzKGVmaXRfZGVxbXNfaW9uX3ZlaF9jbCkpCgpkZXFtc19yZXN1bHRzX2lvbl92ZWhfY2wgPC0gb3V0cHV0UmVzdWx0KGVmaXRfZGVxbXNfaW9uX3ZlaF9jbCwgY29lZl9jb2w9MykKYGBgCgoKYGBge3J9Cmhpc3QoZGVxbXNfcmVzdWx0c19pb25fdmVoX2NsJFAuVmFsdWUpCmhpc3QoZGVxbXNfcmVzdWx0c19pb25fdmVoX2NsJHNjYS5QLlZhbHVlKQoKYGBgCgpgYGB7cn0KcF90aHJlc2hvbGQgPC0gMC4xCnRhYmxlKGRlcW1zX3Jlc3VsdHNfaW9uX3ZlaF9jbCRzY2EuYWRqLnB2YTxwX3RocmVzaG9sZCkKCmRlcW1zX3Jlc3VsdHNfaW9uX3ZlaF9jbCAlPiUgaGVhZCgpCgpkZXFtc19yZXN1bHRzX2lvbl92ZWhfY2wgJT4lCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oJ3VuaXByb3RJRCcpICU+JQogIGdncGxvdChhZXMoeCA9IGxvZ0ZDLCB5ID0gLWxvZzEwKHNjYS5QLlZhbHVlKSwgY29sb3VyID0gc2NhLmFkai5wdmFsIDwgcF90aHJlc2hvbGQpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsPWlmZWxzZShzY2EuYWRqLnB2YWw8cF90aHJlc2hvbGQsIHVuaXByb3RJRCwgTkEpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3cubGVnZW5kPUZBTFNFKSArCiAgdGhlbWVfY2FtcHJvdChib3JkZXI9RkFMU0UsIGJhc2Vfc2l6ZT0xNSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygnZ3JleScsIGdldF9jYXRfcGFsZXR0ZSgyKVsyXSksIG5hbWUgPSAnSU9OIHZzIFZFSCBTaWcuJykgKwogIGxhYnMoeCA9ICdJT05DTCB2cyBWRUhDTCAoTG9nMiknLCB5ID0gJy1sb2cxMChwLXZhbHVlKScpCmBgYApDaGVja2luZyBwcm90ZWluIGxldmVsIGFidW5kYW5jZSBmb3IgUDY3ODA5IChZQlgxKSBhbmQgUDE5MzM4IChOQ0wpCmBgYHtyLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD0zfQpsZnFfcHJvdGVpbltjKCdQMTkzMzgnLCAnUDY3ODA5JyksXSAlPiUKICB0aWR5KGFkZFBoZW5vPVRSVUUpICU+JQogIHNlcGFyYXRlKHNhbXBsZSwgaW50bz1jKE5BLCAndHJlYXRtZW50JywgTkEpKSAlPiUKICBmaWx0ZXIodHJlYXRtZW50ICVpbiUgYygnSU9OJywgJ1ZFSCcpLCBDb25kaXRpb249PSdDTCcpICU+JQogIGdncGxvdChhZXMoZmFjdG9yKHRyZWF0bWVudCwgbGV2ZWxzPWMoJ1ZFSCcsICdJT04nKSksIHZhbHVlKSkgKwogIGdlb21fcG9pbnQoKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBsYWJzKHg9JycsIHk9J1Byb3RlaW4gYWJ1bmRhbmNlJykgKwogIGZhY2V0X3dyYXAofnByb3RlaW4pCmBgYAoK